diff --git a/.clang-format b/.clang-format
index 762ac959fa307f188f4cad00297a4ec9b7aeadd3..0ac489959f851bf8930e40fc9eec547d23c4909b 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,5 +1,10 @@
 ---
 Language:        Cpp
+
+ColumnLimit:     90                   # line width, 0: respect programmer's choice
+TabWidth:        8
+UseTab:          Never
+
 AccessModifierOffset: -2
 AlignAfterOpenBracket: Align
 AlignConsecutiveAssignments: false
@@ -16,7 +21,6 @@ AllowShortLoopsOnASingleLine: true
 AlwaysBreakAfterDefinitionReturnType: None
 AlwaysBreakAfterReturnType: None
 AlwaysBreakBeforeMultilineStrings: true
-AlwaysBreakTemplateDeclarations: true
 BinPackArguments: true
 BinPackParameters: true
 BraceWrapping:
@@ -36,7 +40,7 @@ BreakBeforeBraces: Attach
 BreakBeforeTernaryOperators: true
 BreakConstructorInitializersBeforeComma: true
 # BreakInheritanceListBeforeComma: true
-ColumnLimit:     90
+
 CommentPragmas:  '^ IWYU pragma:'
 ConstructorInitializerAllOnOneLineOrOnePerLine: false
 ConstructorInitializerIndentWidth: 4
@@ -84,6 +88,8 @@ SpacesInCStyleCastParentheses: false
 SpacesInParentheses: false
 SpacesInSquareBrackets: false
 Standard:        Auto
-TabWidth:        8
-UseTab:          Never
+
+# If true, always break after the template<...> of a template declaration.
+AlwaysBreakTemplateDeclarations: true
+
 ...
diff --git a/.gitignore b/.gitignore
index f578f3b11eb6af2d90ff02ccae2eb27def542546..5cef2aaf9a5d56626a6dc4a4258774dfd9b3bba0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
 *gcov
 **/*~
 **/*.bak
-**/*log
 build/
 Framework/Particles/GeneratedParticleProperties.inc
 flymd.html
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1086812c77b0be27f773c27c2235135ace04b884..1bc50e665c00b23c53ef16812db92fba158991c2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,5 @@
 variables:
-  GIT_STRATEGY: fetch
+  GIT_STRATEGY: fetch # clone: fresh clone, fetch: update
   GIT_SSL_NO_VERIFY: "1"
   GIT_DEPTH: "1"
   # to re-use clones also in different forks
@@ -13,7 +13,8 @@ variables:
   # location of AirShowerPhysics/corsika-data
   CORSIKA_DATA: "${CI_PROJECT_DIR}/Data" # the git submodule
   # _alternatively_ corsika-data can be downloaded as submodule:
-  GIT_SUBMODULE_STRATEGY: normal
+  GIT_SUBMODULE_STRATEGY: normal # none: we get the submodules in before_script,
+                                 # normal: get submodules automatically
 
 
 #
@@ -79,11 +80,7 @@ check-clang-format:
   stage: config
   tags:
     - corsika
-  variables:
-  before_script:
-   - cd Data && for F in `find . -name "*.bz2" | grep -v ".git"`; do [ ! -f ${F%%.bz2} ] && bunzip2 -vk $F; done
-   - cd ${CI_PROJECT_DIR}
-  script:
+  script:    
     - mkdir -p build
     - cd build
     - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_PYTHIA=ON
@@ -94,7 +91,6 @@ check-clang-format:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull-push
 
@@ -138,7 +134,6 @@ config-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull-push
 
@@ -195,7 +190,6 @@ build-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull-push
 
@@ -252,7 +246,6 @@ test-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull-push
 
@@ -305,7 +298,6 @@ build_test-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
 
@@ -365,7 +357,6 @@ example-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
 
@@ -415,7 +406,6 @@ build_test_example-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
 
@@ -470,7 +460,6 @@ install-clang-8:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
   artifacts:
@@ -540,7 +529,6 @@ coverage:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
     key: "${CI_COMMIT_REF_SLUG}-gcc"
@@ -580,7 +568,6 @@ documentation:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
     key: "${CI_COMMIT_REF_SLUG}-gcc"
@@ -613,7 +600,6 @@ sanity:
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
-      - ${CI_PROJECT_DIR}/Data/
     untracked: true
     policy: pull
     key: "${CI_COMMIT_REF_SLUG}-gcc"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82a2b8115df86cde009929b01d5c9b64230d89e1..c67782af870c5ca65c03664a62d0bbb24ee808a5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,12 +16,21 @@ project (
   LANGUAGES CXX
   )
 
+include (FeatureSummary)
+
 # as long as there still are modules using it:
 enable_language (Fortran)
 
 # TEMPORARY: this should be removed, the sanitizers should be always enabled
 option (WITH_CORSIKA_SANITIZERS_ENABLED "temporary way to globally disable sanitizers until the currently failing tests are fixed" OFF)
+add_feature_info (CORSIKA_SANITIZERS_ENABLED WITH_CORSIKA_SANITIZERS_ENABLED "Switch to run c++ sanitzers on CORSIKA objects and code.")
+
 option (WITH_COAST "Flag to switch on/off COAST (reverse) interface" OFF)
+add_feature_info (COAST WITH_COAST "The COAST interface, so that you can write C8 processes to run inside C7.")
+
+# HISTORY option selection
+option (WITH_HISTORY "Flag to switch on/off HISTORY" ON)
+add_feature_info (HISTORY WITH_HISTORY "The Foo feature provides very cool stuffdddd.")
 
 # check for python
 set (Python_ADDITIONAL_VERSIONS 3)
@@ -156,5 +165,4 @@ endif ()
 
 
 # final summary output
-include (FeatureSummary)
 feature_summary (WHAT ALL)
diff --git a/Documentation/Examples/CMakeLists.txt b/Documentation/Examples/CMakeLists.txt
index 3e933ea6f97282fb6a5f0f08a55f6967b8d1a996..17c711669f514a311dbaa9297139ca6d26959f8b 100644
--- a/Documentation/Examples/CMakeLists.txt
+++ b/Documentation/Examples/CMakeLists.txt
@@ -9,12 +9,12 @@ CORSIKA_ADD_EXAMPLE (geometry_example)
 target_link_libraries (geometry_example CORSIKAgeometry CORSIKAunits)
 
 CORSIKA_ADD_EXAMPLE (stack_example)
-target_link_libraries (stack_example SuperStupidStack CORSIKAunits)
+target_link_libraries (stack_example CORSIKAsetup CORSIKAunits)
 
 # address sanitizer is making this example too slow, so we only do "undefined"
 CORSIKA_ADD_EXAMPLE (boundary_example)
 target_link_libraries (boundary_example
-  SuperStupidStack
+  CORSIKAsetup
   CORSIKAunits
   CORSIKAlogging
   CORSIKArandom
@@ -34,7 +34,7 @@ target_link_libraries (boundary_example
 
 CORSIKA_ADD_EXAMPLE (cascade_example)
 target_link_libraries (cascade_example
-  SuperStupidStack
+  CORSIKAsetup
   CORSIKAunits
   CORSIKAlogging
   CORSIKArandom
@@ -59,7 +59,7 @@ target_link_libraries (cascade_example
 if (Pythia8_FOUND)
   CORSIKA_ADD_EXAMPLE (cascade_proton_example)
   target_link_libraries (cascade_proton_example
-    SuperStupidStack
+    CORSIKAsetup
     CORSIKAunits
     CORSIKAlogging
     CORSIKArandom
@@ -87,10 +87,15 @@ if (Pythia8_FOUND)
 
   CORSIKA_ADD_EXAMPLE (vertical_EAS RUN_OPTIONS 4 2 10000.)
   target_link_libraries (vertical_EAS
-    SuperStupidStack
+    CORSIKAsetup
     CORSIKAunits
     CORSIKAlogging
     CORSIKArandom
+    CORSIKAhistory
+    ProcessSibyll
+    ProcessPythia8
+    ProcessUrQMD
+    ProcessSwitch
     CORSIKAcascade
     ProcessProposal
     ProcessPythia8
@@ -110,12 +115,13 @@ if (Pythia8_FOUND)
     CORSIKAgeometry
     CORSIKAenvironment
     CORSIKAprocesssequence
+    CORSIKAhistory # for HistoryObservationPlane
     )
 endif()
 
 CORSIKA_ADD_EXAMPLE (stopping_power stopping_power)
 target_link_libraries (stopping_power
-  SuperStupidStack
+  CORSIKAsetup
   CORSIKAunits
   ProcessEnergyLoss
   CORSIKAparticles
diff --git a/Documentation/Examples/boundary_example.cc b/Documentation/Examples/boundary_example.cc
index 26a8239bda592c1b9d45ed2536677cdf31f00e34..40c60ebed365e24d8e4aed8595968581fa7252e7 100644
--- a/Documentation/Examples/boundary_example.cc
+++ b/Documentation/Examples/boundary_example.cc
@@ -81,6 +81,9 @@ private:
 // The example main program for a particle cascade
 //
 int main() {
+
+  std::cout << "boundary_example" << std::endl;
+
   feenableexcept(FE_INVALID);
   // initialize random number sequence(s)
   random::RNGManager::GetInstance().RegisterRandomStream("cascade");
diff --git a/Documentation/Examples/cascade_example.cc b/Documentation/Examples/cascade_example.cc
index 03ebe5e69cf0737e78445956c7d2b6350969c48a..fe8e05d87460d23d3517f4028a686198205dff30 100644
--- a/Documentation/Examples/cascade_example.cc
+++ b/Documentation/Examples/cascade_example.cc
@@ -56,6 +56,8 @@ using namespace corsika::units::si;
 //
 int main() {
 
+  std::cout << "cascade_example" << std::endl;
+
   const LengthType height_atmosphere = 112.8_km;
 
   feenableexcept(FE_INVALID);
diff --git a/Documentation/Examples/cascade_proton_example.cc b/Documentation/Examples/cascade_proton_example.cc
index 12f758a0d02653905a62df5d0d229b395cf74c59..6050f7fcfae52c7a5f77e76928abbf44113c276d 100644
--- a/Documentation/Examples/cascade_proton_example.cc
+++ b/Documentation/Examples/cascade_proton_example.cc
@@ -58,6 +58,9 @@ using namespace corsika::units::si;
 // The example main program for a particle cascade
 //
 int main() {
+
+  std::cout << "cascade_proton_example" << std::endl;
+
   feenableexcept(FE_INVALID);
   // initialize random number sequence(s)
   random::RNGManager::GetInstance().RegisterRandomStream("cascade");
diff --git a/Documentation/Examples/em_shower.cc b/Documentation/Examples/em_shower.cc
index 5840bc597219d7bfcebb957884dc78b26f512b7c..1c5c07cd483242cceed18aa356f4167a0253abb5 100644
--- a/Documentation/Examples/em_shower.cc
+++ b/Documentation/Examples/em_shower.cc
@@ -15,7 +15,6 @@
 #include <corsika/geometry/Sphere.h>
 #include <corsika/process/ProcessSequence.h>
 #include <corsika/process/StackProcess.h>
-#include <corsika/process/interaction_counter/InteractionCounter.h>
 #include <corsika/process/longitudinal_profile/LongitudinalProfile.h>
 #include <corsika/process/observation_plane/ObservationPlane.h>
 #include <corsika/process/particle_cut/ParticleCut.h>
@@ -28,6 +27,7 @@
 #include <corsika/setup/SetupTrajectory.h>
 #include <corsika/units/PhysicalUnits.h>
 #include <corsika/utl/CorsikaFenv.h>
+#include <corsika/process/interaction_counter/InteractionCounter.hpp>
 
 #include <iomanip>
 #include <iostream>
@@ -107,8 +107,8 @@ int main(int argc, char** argv) {
   auto const observationHeight = 1.4_km + builder.getEarthRadius();
   auto const injectionHeight = 112.75_km + builder.getEarthRadius();
   auto const t = -observationHeight * cos(thetaRad) +
-                 sqrt(-si::detail::static_pow<2>(sin(thetaRad) * observationHeight) +
-                      si::detail::static_pow<2>(injectionHeight));
+                 sqrt(-static_pow<2>(sin(thetaRad) * observationHeight) +
+                      static_pow<2>(injectionHeight));
   Point const showerCore{rootCS, 0_m, 0_m, observationHeight};
   Point const injectionPos =
       showerCore +
@@ -159,10 +159,9 @@ int main(int argc, char** argv) {
   cut.ShowResults();
   em_continuous.ShowResults();
   observationLevel.ShowResults();
-  cout << "Cascade energy cut: " << EAS.GetEnergyCut() / 1_GeV << " GeV" << endl;
   const HEPEnergyType Efinal = cut.GetCutEnergy() + cut.GetInvEnergy() +
                                cut.GetEmEnergy() + em_continuous.GetEnergyLost() +
-                               observationLevel.GetEnergyGround() + EAS.GetEnergyCut();
+                               observationLevel.GetEnergyGround();
   cout << "total cut energy (GeV): " << Efinal / 1_GeV << endl
        << "relative difference (%): " << (Efinal / E0 - 1) * 100 << endl;
   observationLevel.Reset();
diff --git a/Documentation/Examples/geometry_example.cc b/Documentation/Examples/geometry_example.cc
index c4d55058be5218130659d7263fd39a5204e02969..da23d81e02baae5381358ff13b12f93d071c5dfe 100644
--- a/Documentation/Examples/geometry_example.cc
+++ b/Documentation/Examples/geometry_example.cc
@@ -21,6 +21,9 @@ using namespace corsika::geometry;
 using namespace corsika::units::si;
 
 int main() {
+
+  std::cout << "geometry_example" << std::endl;
+
   // define the root coordinate system
   geometry::CoordinateSystem& root =
       geometry::RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
diff --git a/Documentation/Examples/helix_example.cc b/Documentation/Examples/helix_example.cc
index 61759311979810c76a656aa4808112be7a5a8a32..9f3eba67e610c87f63f193b3db3aad5978eda4fc 100644
--- a/Documentation/Examples/helix_example.cc
+++ b/Documentation/Examples/helix_example.cc
@@ -20,6 +20,9 @@ using namespace corsika::geometry;
 using namespace corsika::units::si;
 
 int main() {
+
+  std::cout << "helix_example" << std::endl;
+
   geometry::CoordinateSystem& root =
       geometry::RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
 
diff --git a/Documentation/Examples/particle_list_example.cc b/Documentation/Examples/particle_list_example.cc
index 8fb4d45a84d3282fb3d893c296d48aed9ff63000..342c08e7ff6e7cceec63f2aa3583f5fb46199297 100644
--- a/Documentation/Examples/particle_list_example.cc
+++ b/Documentation/Examples/particle_list_example.cc
@@ -25,6 +25,9 @@ using namespace std;
 // The example main program for a particle list
 //
 int main() {
+
+  std::cout << "particle_list_example" << std::endl;
+
   cout << "------------------------------------------"
        << "particles in CORSIKA"
        << "------------------------------------------" << endl;
diff --git a/Documentation/Examples/stack_example.cc b/Documentation/Examples/stack_example.cc
index 0b8d5b3a31cd2997cb5a689df8aa22c391f6eb2a..cc638dbe8e622f27db71210a109784a0f4de7eff 100644
--- a/Documentation/Examples/stack_example.cc
+++ b/Documentation/Examples/stack_example.cc
@@ -36,7 +36,7 @@ void fill(corsika::stack::super_stupid::SuperStupidStack& s) {
 }
 
 void read(corsika::stack::super_stupid::SuperStupidStack& s) {
-  assert(s.GetSize() == 11); // stack has 11 particles
+  assert(s.getEntries() == 11); // stack has 11 particles
 
   HEPEnergyType total_energy;
   int i = 0;
@@ -49,6 +49,9 @@ void read(corsika::stack::super_stupid::SuperStupidStack& s) {
 }
 
 int main() {
+
+  std::cout << "stack_example" << std::endl;
+
   corsika::stack::super_stupid::SuperStupidStack s;
   fill(s);
   read(s);
diff --git a/Documentation/Examples/staticsequence_example.cc b/Documentation/Examples/staticsequence_example.cc
index 8e8542829ea91132635f476a0f99498958f1e3ec..ca75fe4b22172488675fddca7601748acf8e3214 100644
--- a/Documentation/Examples/staticsequence_example.cc
+++ b/Documentation/Examples/staticsequence_example.cc
@@ -100,6 +100,9 @@ void modular() {
 }
 
 int main() {
+
+  std::cout << "staticsequence_example" << std::endl;
+
   modular();
   return 0;
 }
diff --git a/Documentation/Examples/stopping_power.cc b/Documentation/Examples/stopping_power.cc
index 6054b38ac9c0da1ec5fe34143fbebd7683cff7aa..100594f6a0060a5b58ea5561586a0ee13af4d678 100644
--- a/Documentation/Examples/stopping_power.cc
+++ b/Documentation/Examples/stopping_power.cc
@@ -33,6 +33,9 @@ using namespace corsika::units::si;
 // This example demonstrates the energy loss of muons as function of beta*gamma (=p/m)
 //
 int main() {
+
+  std::cout << "stopping_power" << std::endl;
+
   feenableexcept(FE_INVALID);
 
   // setup environment, geometry
diff --git a/Documentation/Examples/vertical_EAS.cc b/Documentation/Examples/vertical_EAS.cc
index 9e833addea3f599f6835bd511062540fe7de58cf..5ceb0de05c8fd9d401289d30aa7a80858facabbf 100644
--- a/Documentation/Examples/vertical_EAS.cc
+++ b/Documentation/Examples/vertical_EAS.cc
@@ -6,6 +6,12 @@
  * 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>
@@ -14,10 +20,10 @@
 #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/interaction_counter/InteractionCounter.h>
 #include <corsika/process/longitudinal_profile/LongitudinalProfile.h>
 #include <corsika/process/observation_plane/ObservationPlane.h>
 #include <corsika/process/on_shell_check/OnShellCheck.h>
@@ -41,7 +47,6 @@
 #include <iostream>
 #include <limits>
 #include <string>
-#include <typeinfo>
 
 using namespace corsika;
 using namespace corsika::process;
@@ -55,7 +60,7 @@ using namespace corsika::environment;
 using namespace std;
 using namespace corsika::units::si;
 
-void registerRandomStreams() {
+void registerRandomStreams(const int seed) {
   random::RNGManager::GetInstance().RegisterRandomStream("cascade");
   random::RNGManager::GetInstance().RegisterRandomStream("qgsjet");
   random::RNGManager::GetInstance().RegisterRandomStream("sibyll");
@@ -63,17 +68,29 @@ void registerRandomStreams() {
   random::RNGManager::GetInstance().RegisterRandomStream("urqmd");
   random::RNGManager::GetInstance().RegisterRandomStream("proposal");
 
-  random::RNGManager::GetInstance().SeedAll();
+  if (seed == 0)
+    random::RNGManager::GetInstance().SeedAll();
+  else
+    random::RNGManager::GetInstance().SeedAll(seed);
 }
 
 int main(int argc, char** argv) {
-  if (argc != 4) {
-    std::cerr << "usage: vertical_EAS <A> <Z> <energy/GeV>" << std::endl;
+
+  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();
+  registerRandomStreams(seed);
 
   // setup environment, geometry
   using EnvType = Environment<setup::IEnvironmentModel>;
@@ -122,8 +139,8 @@ int main(int argc, char** argv) {
   auto const observationHeight = 0_km + builder.getEarthRadius();
   auto const injectionHeight = 112.75_km + builder.getEarthRadius();
   auto const t = -observationHeight * cos(thetaRad) +
-                 sqrt(-si::detail::static_pow<2>(sin(thetaRad) * observationHeight) +
-                      si::detail::static_pow<2>(injectionHeight));
+                 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 +
@@ -223,10 +240,9 @@ int main(int argc, char** argv) {
   cut.ShowResults();
   em_continuous.ShowResults();
   observationLevel.ShowResults();
-  cout << "Cascade energy cut: " << EAS.GetEnergyCut() / 1_GeV << " GeV" << endl;
   const HEPEnergyType Efinal = cut.GetCutEnergy() + cut.GetInvEnergy() +
                                cut.GetEmEnergy() + em_continuous.GetEnergyLost() +
-                               observationLevel.GetEnergyGround() + EAS.GetEnergyCut();
+                               observationLevel.GetEnergyGround();
   cout << "total cut energy (GeV): " << Efinal / 1_GeV << endl
        << "relative difference (%): " << (Efinal / E0 - 1) * 100 << endl;
   observationLevel.Reset();
diff --git a/Environment/testEnvironment.cc b/Environment/testEnvironment.cc
index 55f316e1b5bfeb73a85aed6c2833447158d03754..088dacb1c281befe76301145f1e5de4992e7d085 100644
--- a/Environment/testEnvironment.cc
+++ b/Environment/testEnvironment.cc
@@ -51,7 +51,7 @@ TEST_CASE("FlatExponential") {
 
   Vector const axis(gCS, QuantityVector<dimensionless_d>(0, 0, 1));
   LengthType const lambda = 3_m;
-  auto const rho0 = 1_g / units::si::detail::static_pow<3>(1_cm);
+  auto const rho0 = 1_g / units::static_pow<3>(1_cm);
   FlatExponential<IMediumModel> const medium(gOrigin, axis, rho0, lambda,
                                              protonComposition);
   auto const tEnd = 5_s;
@@ -106,7 +106,7 @@ TEST_CASE("SlidingPlanarExponential") {
                                              std::vector<float>{1.f});
 
   LengthType const lambda = 3_m;
-  auto const rho0 = 1_g / units::si::detail::static_pow<3>(1_cm);
+  auto const rho0 = 1_g / units::static_pow<3>(1_cm);
   auto const tEnd = 5_s;
 
   SlidingPlanarExponential<IMediumModel> const medium(gOrigin, rho0, lambda,
@@ -145,8 +145,7 @@ struct Exponential {
 
   template <int N>
   auto Derivative(Point const& p, Vector<dimensionless_d> const& v) const {
-    return v.GetComponents()[0] * (*this)(p) /
-           corsika::units::si::detail::static_pow<N>(1_m);
+    return v.GetComponents()[0] * (*this)(p) / corsika::units::static_pow<N>(1_m);
   }
 
   auto FirstDerivative(Point const& p, Vector<dimensionless_d> const& v) const {
diff --git a/Framework/Cascade/CMakeLists.txt b/Framework/Cascade/CMakeLists.txt
index 1d1e7e60da4e25e6558683e5ca0183e09e183fe4..9bb6019340dc8578c4f9016091e0cc2c1de0162f 100644
--- a/Framework/Cascade/CMakeLists.txt
+++ b/Framework/Cascade/CMakeLists.txt
@@ -18,6 +18,7 @@ CORSIKA_COPY_HEADERS_TO_NAMESPACE (CORSIKAcascade ${CORSIKAcascade_NAMESPACE} ${
 target_link_libraries(
   CORSIKAcascade
   INTERFACE
+  CORSIKAsetup
   CORSIKArandom
   CORSIKAstackinterface
   CORSIKAparticles
diff --git a/Framework/Cascade/Cascade.h b/Framework/Cascade/Cascade.h
index 653dc62ff43ec90d89fb6191e7581f557778ea76..b520acddde752d6e4b0db7d3cdf711f154b1689c 100644
--- a/Framework/Cascade/Cascade.h
+++ b/Framework/Cascade/Cascade.h
@@ -16,6 +16,8 @@
 #include <corsika/random/UniformRealDistribution.h>
 #include <corsika/stack/SecondaryView.h>
 #include <corsika/units/PhysicalUnits.h>
+#include <corsika/stack/history/EventType.hpp>
+#include <corsika/stack/history/HistorySecondaryProducer.hpp>
 
 #include <corsika/setup/SetupTrajectory.h>
 
@@ -29,9 +31,7 @@
 
 #include <cassert>
 #include <cmath>
-#include <iostream>
 #include <limits>
-#include <type_traits>
 
 /**
  * The cascade namespace assembles all objects needed to simulate full particles cascades.
@@ -61,8 +61,9 @@ namespace corsika::cascade {
 
   template <typename TTracking, typename TProcessList, typename TStack,
             /*
-              TStackView is needed as template parameter because of issue 161 and the
-              inability of clang to understand "MakeView" so far.
+              TStackView is needed as explicit template parameter because
+              of issue 161 and the
+              inability of clang to understand "stack::MakeView" so far.
              */
             typename TStackView = corsika::setup::StackView>
   class Cascade {
@@ -71,6 +72,17 @@ namespace corsika::cascade {
         std::remove_pointer_t<decltype(((Particle*)nullptr)->GetNode())>;
     using MediumInterface = typename VolumeTreeNode::IModelProperties;
 
+  private:
+    // Data members
+    corsika::environment::Environment<MediumInterface> const& fEnvironment;
+    TTracking& fTracking;
+    TProcessList& fProcessSequence;
+    TStack& fStack;
+    corsika::random::RNG& fRNG =
+        corsika::random::RNGManager::GetInstance().GetRandomStream("cascade");
+    unsigned int count_ = 0;
+
+  private:
     // we only want fully configured objects
     Cascade() = delete;
 
@@ -85,9 +97,12 @@ namespace corsika::cascade {
         , fTracking(tr)
         , fProcessSequence(pl)
         , fStack(stack)
-        , energy_cut_(0 * corsika::units::si::electronvolt) {}
-
-    corsika::units::si::HEPEnergyType GetEnergyCut() const { return energy_cut_; }
+        , count_(0) {
+      C8LOG_INFO(c8_ascii_);
+#ifdef WITH_HISTORY
+      C8LOG_INFO(" - With full cascade HISTORY.");
+#endif
+    }
 
     /**
      * set the nodes for all particles on the stack according to their numerical
@@ -110,10 +125,15 @@ namespace corsika::cascade {
 
       while (!fStack.IsEmpty()) {
         while (!fStack.IsEmpty()) {
+          C8LOG_TRACE(fmt::format("Stack: {}", fStack.as_string()));
+          count_++;
           auto pNext = fStack.GetNextParticle();
-          std::cout << "========= next: " << pNext.GetPID() << std::endl;
+          C8LOG_DEBUG(fmt::format(
+              "============== next particle : count={}, pid={}, "
+              ", stack entries={}"
+              ", stack deleted={}",
+              count_, pNext.GetPID(), fStack.getEntries(), fStack.getDeleted()));
           Step(pNext);
-          std::cout << "========= stack ============" << std::endl;
           fProcessSequence.DoStack(fStack);
         }
         // do cascade equations, which can put new particles on Stack,
@@ -128,11 +148,10 @@ namespace corsika::cascade {
      * want to call forceInteraction() for the primary interaction.
      */
     void forceInteraction() {
-      std::cout << "forced interaction!" << std::endl;
+      C8LOG_DEBUG("forced interaction!");
       auto vParticle = fStack.GetNextParticle();
       TStackView secondaries(vParticle);
-      auto projectile = secondaries.GetProjectile();
-      interaction(vParticle, projectile);
+      interaction(vParticle, secondaries);
       fProcessSequence.DoSecondaries(secondaries);
       vParticle.Delete(); // todo: this should be reviewed, see below
     }
@@ -164,8 +183,11 @@ namespace corsika::cascade {
       corsika::random::ExponentialDistribution expDist(1 / total_inv_lambda);
       GrammageType const next_interact = expDist(fRNG);
 
-      std::cout << "total_inv_lambda=" << total_inv_lambda
-                << ", next_interact=" << next_interact << std::endl;
+      C8LOG_DEBUG(
+          "total_lambda={} g/cm2, "
+          ", next_interact={} g/cm2",
+          double((1. / total_inv_lambda) / 1_g * 1_cm * 1_cm),
+          double(next_interact / 1_g * 1_cm * 1_cm));
 
       auto const* currentLogicalNode = vParticle.GetNode();
 
@@ -181,7 +203,7 @@ namespace corsika::cascade {
 
       // determine the maximum geometric step length from continuous processes
       LengthType const distance_max = fProcessSequence.MaxStepLength(vParticle, step);
-      std::cout << "distance_max=" << distance_max << std::endl;
+      C8LOG_DEBUG("distance_max={} m", distance_max / 1_m);
 
       // determine combined total inverse decay time
       InverseTimeType const total_inv_lifetime =
@@ -190,8 +212,10 @@ namespace corsika::cascade {
       // sample random exponential decay time
       corsika::random::ExponentialDistribution expDistDecay(1 / total_inv_lifetime);
       TimeType const next_decay = expDistDecay(fRNG);
-      std::cout << "total_inv_lifetime=" << total_inv_lifetime
-                << ", next_decay=" << next_decay << std::endl;
+      C8LOG_DEBUG(
+          "total_lifetime={} s"
+          ", next_decay={} s",
+          (1 / total_inv_lifetime) / 1_s, next_decay / 1_s);
 
       // convert next_decay from time to length [m]
       LengthType const distance_decay = next_decay * vParticle.GetMomentum().norm() /
@@ -201,7 +225,7 @@ namespace corsika::cascade {
       auto const min_distance =
           std::min({distance_interact, distance_decay, distance_max, geomMaxLength});
 
-      std::cout << " move particle by : " << min_distance << std::endl;
+      C8LOG_DEBUG("transport particle by : {} m", min_distance / 1_m);
 
       // here the particle is actually moved along the trajectory to new position:
       // std::visit(setup::ParticleUpdate<Particle>{vParticle}, step);
@@ -215,15 +239,14 @@ namespace corsika::cascade {
       process::EProcessReturn status = fProcessSequence.DoContinuous(vParticle, step);
 
       if (status == process::EProcessReturn::eParticleAbsorbed) {
-        std::cout << "Cascade: delete absorbed particle " << vParticle.GetPID() << " "
-                  << vParticle.GetEnergy() / 1_GeV << "GeV" << std::endl;
-        energy_cut_ += vParticle.GetEnergy();
+        C8LOG_DEBUG("Cascade: delete absorbed particle PID={} E={} GeV",
+                    vParticle.GetPID(), vParticle.GetEnergy() / 1_GeV);
         vParticle.Delete();
         return;
       }
 
-      std::cout << "sth. happening before geometric limit ? "
-                << ((min_distance < geomMaxLength) ? "yes" : "no") << std::endl;
+      C8LOG_DEBUG("sth. happening before geometric limit ? {}",
+                  ((min_distance < geomMaxLength) ? "yes" : "no"));
 
       if (min_distance < geomMaxLength) { // interaction to happen within geometric limit
 
@@ -241,19 +264,19 @@ namespace corsika::cascade {
             to 'vParticle' above this line. However,
             projectil.AddSecondaries populate the SecondaryView, which can
             then be used afterwards for further processing. Thus: it is
-            important to use projectle (and not vParticle) for Interaction,
+            important to use projectle/view (and not vParticle) for Interaction,
             and Decay!
           */
 
           [[maybe_unused]] auto projectile = secondaries.GetProjectile();
 
           if (min_distance == distance_interact) {
-            interaction(vParticle, projectile);
+            interaction(vParticle, secondaries);
           } else {
             assert(min_distance == distance_decay);
-            decay(vParticle, projectile);
+            decay(vParticle, secondaries);
             // make sure particle actually did decay if it should have done so
-            if (secondaries.GetSize() == 1 &&
+            if (secondaries.getSize() == 1 &&
                 projectile.GetPID() == secondaries.GetNextParticle().GetPID())
               throw std::runtime_error(
                   fmt::format("Cascade: {} decayed into itself!",
@@ -261,15 +284,10 @@ namespace corsika::cascade {
           }
 
           fProcessSequence.DoSecondaries(secondaries);
-          vParticle.Delete(); // todo: this should be reviewed. Where
-                              // exactly are particles best deleted, and
-                              // where they should NOT be
-                              // deleted... maybe Delete function should
-                              // be "protected" and not accessible to physics
+          vParticle.Delete();
 
         } else { // step-length limitation within volume
-
-          std::cout << "step-length limitation" << std::endl;
+          C8LOG_DEBUG("step-length limitation");
           // no extra physics happens here. just proceed to next step.
         }
 
@@ -283,10 +301,7 @@ namespace corsika::cascade {
         };
 
         assert(assertion()); // numerical and logical nodes don't match
-
-      } else { // boundary crossing, step is limited by volume boundary
-
-        std::cout << "boundary crossing! next node = " << nextVol << std::endl;
+      } else {               // boundary crossing, step is limited by volume boundary
         vParticle.SetNode(nextVol);
         /*
           DoBoundary may delete the particle (or not)
@@ -299,9 +314,8 @@ namespace corsika::cascade {
       }
     }
 
-    auto decay(Particle& particle,
-               decltype(std::declval<TStackView>().GetProjectile()) projectile) {
-      std::cout << "decay" << std::endl;
+    auto decay(Particle& particle, TStackView& view) {
+      C8LOG_DEBUG("decay");
       units::si::InverseTimeType const actual_decay_time =
           fProcessSequence.GetTotalInverseLifetime(particle);
 
@@ -309,13 +323,14 @@ namespace corsika::cascade {
           actual_decay_time);
       const auto sample_process = uniDist(fRNG);
       units::si::InverseTimeType inv_decay_count = units::si::InverseTimeType::zero();
-      return fProcessSequence.SelectDecay(particle, projectile, sample_process,
-                                          inv_decay_count);
+      auto const returnCode =
+          fProcessSequence.SelectDecay(particle, view, sample_process, inv_decay_count);
+      SetEventType(view, history::EventType::Decay);
+      return returnCode;
     }
 
-    auto interaction(Particle& particle,
-                     decltype(std::declval<TStackView>().GetProjectile()) projectile) {
-      std::cout << "collide" << std::endl;
+    auto interaction(Particle& particle, TStackView& view) {
+      C8LOG_DEBUG("collide");
 
       units::si::InverseGrammageType const current_inv_length =
           fProcessSequence.GetTotalInverseInteractionLength(particle);
@@ -324,20 +339,30 @@ namespace corsika::cascade {
           current_inv_length);
       const auto sample_process = uniDist(fRNG);
       auto inv_lambda_count = units::si::InverseGrammageType::zero();
-      return fProcessSequence.SelectInteraction(particle, projectile, sample_process,
-                                                inv_lambda_count);
+      auto const returnCode = fProcessSequence.SelectInteraction(
+          particle, view, sample_process, inv_lambda_count);
+      SetEventType(view, history::EventType::Interaction);
+      return returnCode;
     }
 
-  private:
-    corsika::environment::Environment<MediumInterface> const& fEnvironment;
-    TTracking& fTracking;
-    TProcessList& fProcessSequence;
-    TStack& fStack;
-    corsika::random::RNG& fRNG =
-        corsika::random::RNGManager::GetInstance().GetRandomStream("cascade");
-
-    corsika::units::si::HEPEnergyType energy_cut_;
+    void SetEventType(TStackView& view, [[maybe_unused]] history::EventType eventType) {
+      if constexpr (TStackView::has_event) {
+        for (auto&& sec : view) { sec.GetEvent()->setEventType(eventType); }
+      }
+    }
 
-  }; // namespace corsika::cascade
+    // but this here temporarily. Should go into dedicated file later:
+    const char* c8_ascii_ =
+        R"V0G0N(
+  ,ad8888ba,     ,ad8888ba,    88888888ba    ad88888ba   88  88      a8P          db              ad88888ba   
+ d8"'    `"8b   d8"'    `"8b   88      "8b  d8"     "8b  88  88    ,88'          d88b            d8"     "8b  
+d8'            d8'        `8b  88      ,8P  Y8,          88  88  ,88"           d8'`8b           Y8a     a8P  
+88             88          88  88aaaaaa8P'  `Y8aaaaa,    88  88,d88'           d8'  `8b           "Y8aaa8P"   
+88             88          88  88""""88'      `"""""8b,  88  8888"88,         d8YaaaaY8b          ,d8"""8b,   
+Y8,            Y8,        ,8P  88    `8b            `8b  88  88P   Y8b       d8""""""""8b        d8"     "8b  
+ Y8a.    .a8P   Y8a.    .a8P   88     `8b   Y8a     a8P  88  88     "88,    d8'        `8b       Y8a     a8P  
+  `"Y8888Y"'     `"Y8888Y"'    88      `8b   "Y88888P"   88  88       Y8b  d8'          `8b       "Y88888P"
+	)V0G0N";
+  };
 
 } // namespace corsika::cascade
diff --git a/Framework/Cascade/testCascade.cc b/Framework/Cascade/testCascade.cc
index a22cfbcb4f2746607fd0498c094d85eee3d69f2e..02f974f5245f487c29bd49d00f14216b36312f7d 100644
--- a/Framework/Cascade/testCascade.cc
+++ b/Framework/Cascade/testCascade.cc
@@ -32,7 +32,6 @@ using namespace corsika::units;
 using namespace corsika::units::si;
 using namespace corsika::geometry;
 
-#include <iostream>
 #include <limits>
 using namespace std;
 
@@ -69,18 +68,21 @@ public:
     return fX0;
   }
 
-  template <typename TProjectile>
-  corsika::process::EProcessReturn DoInteraction(TProjectile& vP) {
+  template <typename TSecondaryView>
+  corsika::process::EProcessReturn DoInteraction(TSecondaryView& view) {
     fCalls++;
-    const HEPEnergyType E = vP.GetEnergy();
-    vP.AddSecondary(
+    auto const projectile = view.GetProjectile();
+    const HEPEnergyType E = projectile.GetEnergy();
+    view.AddSecondary(
         std::tuple<particles::Code, units::si::HEPEnergyType,
                    corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            vP.GetPID(), E / 2, vP.GetMomentum(), vP.GetPosition(), vP.GetTime()});
-    vP.AddSecondary(
+            projectile.GetPID(), E / 2, projectile.GetMomentum(),
+            projectile.GetPosition(), projectile.GetTime()});
+    view.AddSecondary(
         std::tuple<particles::Code, units::si::HEPEnergyType,
                    corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            vP.GetPID(), E / 2, vP.GetMomentum(), vP.GetPosition(), vP.GetTime()});
+            projectile.GetPID(), E / 2, projectile.GetMomentum(),
+            projectile.GetPosition(), projectile.GetTime()});
     return EProcessReturn::eInteracted;
   }
 
@@ -106,12 +108,11 @@ public:
       if (E < fEcrit) {
         p.Delete();
         fCount++;
-      } else {
-        ++p; // next particle
       }
+      ++p; // next particle
     }
-    cout << "ProcessCut::DoSecondaries size=" << vS.GetSize() << " count=" << fCount
-         << endl;
+    C8LOG_INFO(fmt::format("ProcessCut::DoSecondaries size={} count={}", vS.getEntries(),
+                           fCount));
     return EProcessReturn::eOk;
   }
 
@@ -162,7 +163,7 @@ TEST_CASE("Cascade", "[Cascade]") {
   SECTION("forced interaction") {
     EAS.SetNodes();
     EAS.forceInteraction();
-    CHECK(stack.GetSize() == 2);
+    CHECK(stack.getEntries() == 2);
     CHECK(split.GetCalls() == 1);
   }
 }
diff --git a/Framework/Cascade/testCascade.h b/Framework/Cascade/testCascade.h
index 41a96ee3e7829aedd3033f7fd8f33c3679cb810d..55740c5601694b2c982c54607bcd4182f519171f 100644
--- a/Framework/Cascade/testCascade.h
+++ b/Framework/Cascade/testCascade.h
@@ -9,23 +9,28 @@
 #pragma once
 
 #include <corsika/environment/Environment.h>
-#include <corsika/setup/SetupStack.h>
+
+#include <corsika/stack/CombinedStack.h>
+#include <corsika/stack/SecondaryView.h>
+#include <corsika/stack/node/GeometryNodeStackExtension.h>
+#include <corsika/stack/nuclear_extension/NuclearStackExtension.h>
 
 using TestEnvironmentType =
     corsika::environment::Environment<corsika::environment::IMediumModel>;
 
 template <typename T>
-using SetupGeometryDataInterface = GeometryDataInterface<T, TestEnvironmentType>;
+using SetupGeometryDataInterface =
+    corsika::stack::node::GeometryDataInterface<T, TestEnvironmentType>;
 
 // combine particle data stack with geometry information for tracking
 template <typename StackIter>
 using StackWithGeometryInterface = corsika::stack::CombinedParticleInterface<
-    corsika::setup::detail::ParticleDataStack::PIType, SetupGeometryDataInterface,
-    StackIter>;
+    corsika::stack::nuclear_extension::ParticleDataStack::MPIType,
+    SetupGeometryDataInterface, StackIter>;
 
 using TestCascadeStack = corsika::stack::CombinedStack<
-    typename corsika::setup::detail::ParticleDataStack::StackImpl,
-    GeometryData<TestEnvironmentType>, StackWithGeometryInterface>;
+    typename corsika::stack::nuclear_extension::ParticleDataStack::StackImpl,
+    corsika::stack::node::GeometryData<TestEnvironmentType>, StackWithGeometryInterface>;
 
 /*
   See also Issue 161
diff --git a/Framework/Geometry/FourVector.h b/Framework/Geometry/FourVector.h
index 418575b07df02d09b236f504f97ddb32980f224f..2ea2b28d42616f07545742d2589528680cd2658c 100644
--- a/Framework/Geometry/FourVector.h
+++ b/Framework/Geometry/FourVector.h
@@ -12,7 +12,6 @@
 #include <corsika/units/PhysicalUnits.h>
 
 #include <iostream>
-#include <type_traits>
 
 namespace corsika::geometry {
 
diff --git a/Framework/Logging/CMakeLists.txt b/Framework/Logging/CMakeLists.txt
index d74849f26c8f1c683899f51517cc150f9a787e3b..8aa0abe3cebd3f9c5b18b05a6082d7ca2c36ef61 100644
--- a/Framework/Logging/CMakeLists.txt
+++ b/Framework/Logging/CMakeLists.txt
@@ -25,7 +25,6 @@ target_include_directories (
   INTERFACE
   $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
   $<INSTALL_INTERFACE:include/>
-  C8::ext:boost
   )
 
 # and link against spdlog
diff --git a/Framework/Logging/Logging.h b/Framework/Logging/Logging.h
index 774bffab01117476f62c66792328d00a9a1a2ce5..814f35de4de5c025c3bab8eaa7fdf51e135e7010 100644
--- a/Framework/Logging/Logging.h
+++ b/Framework/Logging/Logging.h
@@ -40,8 +40,9 @@
 #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_CRITICAL
 #endif
 
+#include <spdlog/fmt/ostr.h> // will output whenerver a streaming operator is found
+#include <spdlog/sinks/stdout_color_sinks.h>
 #include <spdlog/spdlog.h>
-#include "spdlog/sinks/stdout_color_sinks.h"
 
 namespace corsika::logging {
 
diff --git a/Framework/Particles/CMakeLists.txt b/Framework/Particles/CMakeLists.txt
index 3c18f1572c6fd714cd6eb2aea979cedd9d817149..0b0e6bb96a7db32ca1be1eec4828aee4a50ad7bc 100644
--- a/Framework/Particles/CMakeLists.txt
+++ b/Framework/Particles/CMakeLists.txt
@@ -56,6 +56,7 @@ target_link_libraries (
   CORSIKAparticles
   PUBLIC
   CORSIKAunits
+  CORSIKAlogging
   )
 
 set_target_properties (
diff --git a/Framework/Particles/ParticleProperties.h b/Framework/Particles/ParticleProperties.h
index d184e7544257a2bdf700a3d267495e4d34d7f862..d217aa50ba2299136cd8358293be1eb35964e5dd 100644
--- a/Framework/Particles/ParticleProperties.h
+++ b/Framework/Particles/ParticleProperties.h
@@ -17,7 +17,6 @@
 #include <array>
 #include <cstdint>
 #include <iosfwd>
-#include <type_traits>
 
 #include <corsika/units/PhysicalUnits.h>
 
diff --git a/Framework/ProcessSequence/BaseProcess.h b/Framework/ProcessSequence/BaseProcess.h
index 8249d1472efd1d80187f7d3ae8b0f82935750881..533068893a20e0ee55904d5b5ac18662c3fa6ccf 100644
--- a/Framework/ProcessSequence/BaseProcess.h
+++ b/Framework/ProcessSequence/BaseProcess.h
@@ -9,7 +9,6 @@
 #pragma once
 
 #include <corsika/process/ProcessReturn.h> // for convenience
-#include <type_traits>
 
 namespace corsika::process {
 
diff --git a/Framework/ProcessSequence/CMakeLists.txt b/Framework/ProcessSequence/CMakeLists.txt
index a6c2a1701fccba3090b5376a2856090a97a3ae7e..811e2c3864c4d617043586842c03bcc74db3bb6f 100644
--- a/Framework/ProcessSequence/CMakeLists.txt
+++ b/Framework/ProcessSequence/CMakeLists.txt
@@ -23,6 +23,12 @@ set (
 CORSIKA_COPY_HEADERS_TO_NAMESPACE (CORSIKAprocesssequence ${CORSIKAprocesssequence_NAMESPACE} ${CORSIKAprocesssequence_HEADERS})
 
 #include directive for upstream code
+target_link_libraries (
+  CORSIKAprocesssequence
+  INTERFACE
+  CORSIKAsetup
+  )
+
 target_include_directories (
   CORSIKAprocesssequence
   INTERFACE
@@ -48,7 +54,6 @@ CORSIKA_ADD_TEST (testProcessSequence)
 target_link_libraries (
   testProcessSequence
   ProcessSwitch
-  CORSIKAsetup
   CORSIKAgeometry
   CORSIKAprocesssequence
   CORSIKAtesting
diff --git a/Framework/ProcessSequence/ProcessSequence.h b/Framework/ProcessSequence/ProcessSequence.h
index 7555f1800a720d81855edecbd4e3f83728cd444a..ea125461368ae712407070ac56b6d993dff62ea4 100644
--- a/Framework/ProcessSequence/ProcessSequence.h
+++ b/Framework/ProcessSequence/ProcessSequence.h
@@ -20,7 +20,6 @@
 
 #include <cmath>
 #include <limits>
-#include <type_traits>
 
 namespace corsika::process {
 
diff --git a/Framework/ProcessSequence/testProcessSequence.cc b/Framework/ProcessSequence/testProcessSequence.cc
index 0b2855f4ebc6f343ca10ff01b9ec066f5d4865da..b51876886acbd22a80dd20aca4abe5d50c23e216 100644
--- a/Framework/ProcessSequence/testProcessSequence.cc
+++ b/Framework/ProcessSequence/testProcessSequence.cc
@@ -147,7 +147,7 @@ public:
 class Decay1 : public DecayProcess<Decay1> {
 
 public:
-  Decay1(const int v) {
+  Decay1(const int) {
     cout << "Decay1()" << endl;
     globalCount++;
   }
@@ -287,5 +287,5 @@ TEST_CASE("SwitchProcess") {
   Process1 p1(0);
   Process2 p2(1);
   switch_process::SwitchProcess s(p1, p2, 10_GeV);
-  REQUIRE(is_switch_process_v<decltype(s)>);
+  CHECK(is_switch_process_v<decltype(s)>);
 }
diff --git a/Framework/Random/testRandom.cc b/Framework/Random/testRandom.cc
index 14971167d1517e8cef0dfc2a3860f86f25e1c386..d9053f74d63b49a39e24470c052b97970f573b02 100644
--- a/Framework/Random/testRandom.cc
+++ b/Framework/Random/testRandom.cc
@@ -15,7 +15,6 @@
 #include <iostream>
 #include <limits>
 #include <random>
-#include <type_traits>
 
 using namespace corsika::random;
 
diff --git a/Framework/StackInterface/CMakeLists.txt b/Framework/StackInterface/CMakeLists.txt
index cf0970e26816fb76633da1bae0e63d5eecaeb01f..956f1873b6f8781c426ab4ee52d31fac80c72e6f 100644
--- a/Framework/StackInterface/CMakeLists.txt
+++ b/Framework/StackInterface/CMakeLists.txt
@@ -21,6 +21,13 @@ CORSIKA_COPY_HEADERS_TO_NAMESPACE (
   CORSIKAstackinterface ${CORSIKAstackinterface_NAMESPACE} ${CORSIKAstackinterface_HEADERS}
   )
 
+target_link_libraries (
+  CORSIKAstackinterface
+  INTERFACE
+  CORSIKAlogging
+  CORSIKAsetup  
+  )
+
 target_include_directories (
   CORSIKAstackinterface
   INTERFACE
diff --git a/Framework/StackInterface/CombinedStack.h b/Framework/StackInterface/CombinedStack.h
index 2534ced4e89e3b5fc6fc231c57e0d0887d760ee0..d8cecfd57bdf2fe43d549dffd1e4432818a6f961 100644
--- a/Framework/StackInterface/CombinedStack.h
+++ b/Framework/StackInterface/CombinedStack.h
@@ -8,6 +8,7 @@
 
 #pragma once
 
+#include <corsika/logging/Logging.h>
 #include <corsika/particles/ParticleProperties.h>
 #include <corsika/stack/Stack.h>
 #include <corsika/units/PhysicalUnits.h>
@@ -37,10 +38,6 @@ namespace corsika::stack {
   class CombinedParticleInterface
       : public ParticleInterfaceB<ParticleInterfaceA<StackIterator>> {
 
-    // template<template <typename> typename _PI>
-    // template <typename StackDataType, template <typename> typename ParticleInterface>
-    // template<typename T1, template <typename> typename T2> friend class Stack<T1, T2>;
-
     using PI_C =
         CombinedParticleInterface<ParticleInterfaceA, ParticleInterfaceB, StackIterator>;
     using PI_A = ParticleInterfaceA<StackIterator>;
@@ -91,6 +88,10 @@ namespace corsika::stack {
       PI_B::SetParticleData(static_cast<PI_B&>(p), vB);
     }
     ///@}
+
+    std::string as_string() const {
+      return fmt::format("[[{}][{}]]", PI_A::as_string(), PI_B::as_string());
+    }
   };
 
   /**
diff --git a/Framework/StackInterface/ParticleBase.h b/Framework/StackInterface/ParticleBase.h
index 9bda7aebb821cc450604dd9e5d4a42731febe2b1..1bf7d0ad8834eddd51971feffcd4db0fd23fabb6 100644
--- a/Framework/StackInterface/ParticleBase.h
+++ b/Framework/StackInterface/ParticleBase.h
@@ -8,8 +8,6 @@
 
 #pragma once
 
-#include <type_traits>
-
 namespace corsika::stack {
 
   /**
@@ -63,6 +61,11 @@ namespace corsika::stack {
      */
     void Delete() { GetIterator().GetStack().Delete(GetIterator()); }
 
+    /**
+     * Method to retrieve the status of the Particle. Is it already deleted? Or not.
+     */
+    bool isDeleted() const { return GetIterator().GetStack().isDeleted(GetIterator()); }
+
     /**
      * Add a secondary particle based on *this on the stack @param
      * args is a variadic list of input data that has to match the
diff --git a/Framework/StackInterface/SecondaryView.h b/Framework/StackInterface/SecondaryView.h
index 077e56ea2dff557ac0c7f43dc7f9446a2fa0caaf..dcdcab8127e6217bfc4f75d68787ae92b80a9178 100644
--- a/Framework/StackInterface/SecondaryView.h
+++ b/Framework/StackInterface/SecondaryView.h
@@ -10,11 +10,17 @@
 
 #include <corsika/stack/Stack.h>
 
+#include <corsika/logging/Logging.h>
+
 #include <stdexcept>
 #include <vector>
 
 namespace corsika::stack {
 
+  // forward-decl:
+  template <class T1, template <class> class T2>
+  class DefaultSecondaryProducer;
+
   /**
    * @class SecondaryView
    *
@@ -43,26 +49,30 @@ namespace corsika::stack {
      *Further information about implementation (for developers):* All
      data is stored in the original stack privided at construction
      time. The secondary particle (view) indices are stored in an
-     extra std::vector of SecondaryView class 'fIndices' referring to
+     extra std::vector of SecondaryView class 'indices_' referring to
      the original stack slot indices. The index of the primary
      projectle particle is also explicitly stored in
-     'fProjectileIndex'. StackIterator indices
+     'projectile_index_'. StackIterator indices
      'i = StackIterator::GetIndex()' are referring to those numbers,
-     where 'i==0' refers to the 'fProjectileIndex', and
-     'StackIterator::GetIndex()>0' to 'fIndices[i-1]', see function
+     where 'i==0' refers to the 'projectile_index_', and
+     'StackIterator::GetIndex()>0' to 'indices_[i-1]', see function
      GetIndexFromIterator.
    */
 
-  template <typename StackDataType, template <typename> typename ParticleInterface>
-  class SecondaryView : public Stack<StackDataType&, ParticleInterface> {
+  template <typename StackDataType, template <typename> typename ParticleInterface,
+            template <class T1, template <class> class T2> class MSecondaryProducer =
+                DefaultSecondaryProducer>
 
-    using ViewType = SecondaryView<StackDataType, ParticleInterface>;
+  class SecondaryView : public Stack<StackDataType&, ParticleInterface>,
+                        public MSecondaryProducer<StackDataType, ParticleInterface> {
+    using ViewType = SecondaryView<StackDataType, ParticleInterface, MSecondaryProducer>;
 
   private:
     /**
      * Helper type for inside this class
      */
-    using InnerStackType = Stack<StackDataType&, ParticleInterface>;
+    using InnerStackTypeRef = Stack<StackDataType&, ParticleInterface>;
+    using InnerStackTypeRef::getDeleted;
 
     /**
      * @name We need this "special" types with non-reference StackData for
@@ -70,12 +80,16 @@ namespace corsika::stack {
      * @{
      */
     using InnerStackTypeValue = Stack<StackDataType, ParticleInterface>;
+
+  public:
     using StackIteratorValue =
         StackIteratorInterface<typename std::remove_reference<StackDataType>::type,
                                ParticleInterface, InnerStackTypeValue>;
+    using ConstStackIteratorValue =
+        ConstStackIteratorInterface<typename std::remove_reference<StackDataType>::type,
+                                    ParticleInterface, InnerStackTypeValue>;
     /// @}
 
-  public:
     using StackIterator =
         StackIteratorInterface<typename std::remove_reference<StackDataType>::type,
                                ParticleInterface, ViewType>;
@@ -95,6 +109,8 @@ namespace corsika::stack {
     friend class ConstStackIteratorInterface<
         typename std::remove_reference<StackDataType>::type, ParticleInterface, ViewType>;
 
+    friend class ParticleBase<StackIterator>;
+
   private:
     /**
      * This is not accessible, since we don't want to allow creating a
@@ -102,73 +118,208 @@ namespace corsika::stack {
      */
     template <typename... Args>
     SecondaryView(Args... args) = delete;
+    SecondaryView() = delete;
+
+  private:
+    InnerStackTypeValue& inner_stack_;
+    unsigned int projectile_index_;
+    std::vector<unsigned int> indices_;
 
   public:
     /**
        SecondaryView can only be constructed passing it a valid
-       StackIterator to another Stack object
+       StackIterator to another Stack object (here: lvalue)
+     **/
+    SecondaryView(StackIteratorValue& particle)
+        : Stack<StackDataType&, ParticleInterface>(particle.GetStackData())
+        , MSecondaryProducer<StackDataType, ParticleInterface>{particle}
+        , inner_stack_(particle.GetStack())
+        , projectile_index_(particle.GetIndex()) {
+      C8LOG_TRACE("SecondaryView::SecondaryView(particle&)");
+    }
+    /**
+       SecondaryView can only be constructed passing it a valid
+       StackIterator to another Stack object (here: rvalue)
      **/
-    SecondaryView(StackIteratorValue& vI)
-        : Stack<StackDataType&, ParticleInterface>(vI.GetStackData())
-        , fProjectileIndex(vI.GetIndex()) {}
+    SecondaryView(StackIteratorValue&& particle)
+        : Stack<StackDataType&, ParticleInterface>(particle.GetStackData())
+        , MSecondaryProducer<StackDataType, ParticleInterface>{particle}
+        , inner_stack_(particle.GetStack())
+        , projectile_index_(particle.GetIndex()) {
+      C8LOG_TRACE("SecondaryView::SecondaryView(particle&&)");
+    }
+    /**
+     * Also allow to create a new View from a Projectile (StackIterator on View)
+     *
+     * Note, the view generated this way will be equivalent to the orignal view in
+     * terms of reference to the underlying data stack. It is not a "view to a view".
+     */
+    SecondaryView(ViewType& view, StackIterator& projectile)
+        : Stack<StackDataType&, ParticleInterface>{view.GetStackData()}
+        , MSecondaryProducer<StackDataType, ParticleInterface>{StackIteratorValue{
+              view.inner_stack_, view.GetIndexFromIterator(projectile.GetIndex())}}
+        , inner_stack_{view.inner_stack_}
+        , projectile_index_{view.GetIndexFromIterator(projectile.GetIndex())} {
+      C8LOG_TRACE("SecondaryView::SecondaryView(view, projectile)");
+    }
+
+    /**
+     * This returns the projectile/parent in the original Stack, where this
+     * SecondaryView is derived from. This projectile should not be
+     * used to modify the Stack!
+     */
+    ConstStackIteratorValue parent() const {
+      return ConstStackIteratorValue(inner_stack_, projectile_index_);
+    }
 
+    /**
+     * This returns the projectile/parent in the original Stack, where this
+     * SecondaryView is derived from. This projectile should not be
+     * used to modify the Stack!
+     */
+    StackIteratorValue asNewParent() const {
+      return StackIteratorValue(inner_stack_, projectile_index_);
+    }
+
+    /**
+     * This return a projectile of this SecondaryView, which can be
+     * used to modify the SecondaryView
+     */
     StackIterator GetProjectile() {
       // NOTE: 0 is special marker here for PROJECTILE, see GetIndexFromIterator
       return StackIterator(*this, 0);
     }
 
+  public:
+    /**
+     * Method to add a new secondary particle on this SecondaryView
+     */
     template <typename... Args>
-    auto AddSecondary(const Args... v) {
-      StackIterator proj = GetProjectile();
+    StackIterator AddSecondary(const Args... v) {
+      C8LOG_TRACE("SecondaryView::AddSecondary(Args&&)");
+      StackIterator proj = GetProjectile(); // make this const
       return AddSecondary(proj, v...);
     }
 
+  protected:
+    /**
+     * Overwrite of Stack::StackIterator
+     *
+     * increase stack size, create new particle at end of stack,
+     * related to parent particle/projectile
+     *
+     * This should only get internally called from a
+     * StackIterator::AddSecondary via ParticleBase
+     */
     template <typename... Args>
-    auto AddSecondary(StackIterator& proj, const Args... v) {
+    StackIterator AddSecondary(StackIterator& proj, const Args... v) {
+      C8LOG_TRACE("SecondaryView::AddSecondary(StackIterator&, Args&&)");
       // make space on stack
-      InnerStackType::GetStackData().IncrementSize();
+      InnerStackTypeRef::GetStackData().IncrementSize();
+      inner_stack_.deleted_.push_back(false);
       // get current number of secondaries on stack
-      const unsigned int idSec = GetSize();
+      const unsigned int idSec = getSize();
       // determine index on (inner) stack where new particle will be located
-      const unsigned int index = InnerStackType::GetStackData().GetSize() - 1;
-      fIndices.push_back(index);
+      const unsigned int index = InnerStackTypeRef::GetStackData().GetSize() - 1;
+      indices_.push_back(index);
       // NOTE: "+1" is since "0" is special marker here for PROJECTILE, see
       // GetIndexFromIterator
-      return StackIterator(*this, idSec + 1, proj, v...);
+      auto sec = StackIterator(*this, idSec + 1, proj, v...);
+      MSecondaryProducer<StackDataType, ParticleInterface>::new_secondary(sec);
+      return sec;
     }
 
+  public:
     /**
      * overwrite Stack::GetSize to return actual number of secondaries
      */
-    unsigned int GetSize() const { return fIndices.size(); }
+    unsigned int getSize() const { return indices_.size(); }
+    unsigned int getEntries() const { return getSize() - getDeleted(); }
+    bool IsEmpty() const { return getEntries() == 0; }
 
     /**
      * @name These are functions required by std containers and std loops
      * The Stack-versions must be overwritten, since here we need the correct
-     * SecondaryView::GetSize
+     * SecondaryView::getSize
      * @{
      */
     // NOTE: the "+1" is since "0" is special marker here for PROJECTILE, see
     // GetIndexFromIterator
-    auto begin() { return StackIterator(*this, 0 + 1); }
-    auto end() { return StackIterator(*this, GetSize() + 1); }
-    auto last() { return StackIterator(*this, GetSize() - 1 + 1); }
+    StackIterator begin() {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!isDeleted(i)) break;
+      }
+      return StackIterator(*this, i + 1);
+    }
+    auto end() { return StackIterator(*this, getSize() + 1); }
+    auto last() {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!isDeleted(getSize() - 1 - i)) break;
+      }
+      return StackIterator(*this, getSize() - 1 - i + 1);
+    }
 
-    auto begin() const { return ConstStackIterator(*this, 0 + 1); }
-    auto end() const { return ConstStackIterator(*this, GetSize() + 1); }
-    auto last() const { return ConstStackIterator(*this, GetSize() - 1 + 1); }
+    auto begin() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!isDeleted(i)) break;
+      }
+      return ConstStackIterator(*this, i + 1);
+    }
+    auto end() const { return ConstStackIterator(*this, getSize() + 1); }
+    auto last() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!isDeleted(getSize() - 1 - i)) break;
+      }
+      return ConstStackIterator(*this, getSize() - 1 - i + 1);
+    }
 
-    auto cbegin() const { return ConstStackIterator(*this, 0 + 1); }
-    auto cend() const { return ConstStackIterator(*this, GetSize() + 1); }
-    auto clast() const { return ConstStackIterator(*this, GetSize() - 1 + 1); }
+    auto cbegin() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!isDeleted(i)) break;
+      }
+      return ConstStackIterator(*this, i + 1);
+    }
+    auto cend() const { return ConstStackIterator(*this, getSize()); }
+    auto clast() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!isDeleted(getSize() - 1 - i)) break;
+      }
+      return ConstStackIterator(*this, getSize() - 1 - i + 1);
+    }
+    StackIterator at(unsigned int i) { return StackIterator(*this, i); }
+    ConstStackIterator at(unsigned int i) const { return ConstStackIterator(*this, i); }
+    StackIterator first() { return StackIterator{*this, 0}; }
+    ConstStackIterator cfirst() const { return ConstStackIterator{*this, 0}; }
     /// @}
 
+    void Swap(StackIterator a, StackIterator b) {
+      C8LOG_TRACE("View::Swap");
+      inner_stack_.Swap(GetIndexFromIterator(a.GetIndex()),
+                        GetIndexFromIterator(b.GetIndex()));
+    }
+    void Copy(StackIterator a, StackIterator b) {
+      C8LOG_TRACE("View::Copy");
+      inner_stack_.Copy(GetIndexFromIterator(a.GetIndex()),
+                        GetIndexFromIterator(b.GetIndex()));
+    }
+    void Copy(ConstStackIterator a, StackIterator b) {
+      C8LOG_TRACE("View::Copy");
+      inner_stack_.Copy(GetIndexFromIterator(a.GetIndex()),
+                        GetIndexFromIterator(b.GetIndex()));
+    }
+
     /**
      * need overwrite Stack::Delete, since we want to call
      * SecondaryView::DeleteLast
      *
      * The particle is deleted on the underlying (internal) stack. The
-     * local references in SecondaryView in fIndices must be fixed,
+     * local references in SecondaryView in indices_ must be fixed,
      * too.  The approach is to a) check if the particle 'p' is at the
      * very end of the internal stack, b) if not: move it there by
      * copying the last particle to the current particle location, c)
@@ -176,55 +327,151 @@ namespace corsika::stack {
      *
      */
     void Delete(StackIterator p) {
-      if (IsEmpty()) { /* error */
+      C8LOG_TRACE("SecondaryView::Delete");
+      if (IsEmpty()) { /*error*/
         throw std::runtime_error("Stack, cannot delete entry since size is zero");
       }
-      const int innerSize = InnerStackType::GetSize();
-      const int innerIndex = GetIndexFromIterator(p.GetIndex());
-      if (innerIndex < innerSize - 1)
-        InnerStackType::GetStackData().Copy(innerSize - 1,
-                                            GetIndexFromIterator(p.GetIndex()));
-      DeleteLast();
+      if (isDeleted(p.GetIndex() - 1)) { /*error*/
+        throw std::runtime_error("Stack, cannot delete entry since already deleted");
+      }
+      inner_stack_.Delete(GetIndexFromIterator(p.GetIndex()));
+      InnerStackTypeRef::nDeleted_++; // also count in SecondaryView
     }
 
     /**
-     * need overwrite Stack::Delete, since we want to call SecondaryView::DeleteLast
+     * return next particle from stack, need to overwrtie Stack::GetNextParticle to get
+     * right reference
      */
-    void Delete(ParticleInterfaceType p) { Delete(p.GetIterator()); }
+    StackIterator GetNextParticle() {
+      while (purgeLastIfDeleted()) {}
+      return last();
+    }
 
     /**
-     * delete last particle on stack by decrementing stack size
+     * check if this particle was already deleted
+     *
+     * need to re-implement for SecondaryView since StackIterator types are a bit
+     * different
+     */
+    bool isDeleted(const StackIterator& p) const { return isDeleted(p.GetIndex() - 1); }
+    bool isDeleted(const ConstStackIterator& p) const {
+      return isDeleted(p.GetIndex() - 1);
+    }
+    /**
+     * delete this particle
      */
-    void DeleteLast() {
-      fIndices.pop_back();
-      InnerStackType::GetStackData().DecrementSize();
+    bool isDeleted(const ParticleInterfaceType& p) const {
+      return isDeleted(p.GetIterator());
     }
 
     /**
-     * return next particle from stack, need to overwrtie Stack::GetNextParticle to get
-     * right reference
+     * Function to ultimatively remove the last entry from the stack,
+     * if it was marked as deleted before. If this is not the case,
+     * the function will just return false and do nothing.
      */
-    StackIterator GetNextParticle() { return last(); }
+    bool purgeLastIfDeleted() {
+      C8LOG_TRACE("SecondaryView::purgeLastIfDeleted");
+      if (!isDeleted(getSize() - 1))
+        return false; // the last particle is not marked for deletion. Do nothing.
+      inner_stack_.purge(GetIndexFromIterator(getSize()));
+      InnerStackTypeRef::nDeleted_--;
+      indices_.pop_back();
+      return true;
+    }
 
     /**
-     * check if there are no further particles on stack
+     * Function to ultimatively remove all entries from the stack
+     * marked as deleted.
+     *
+     * Careful: this will re-order the entries on the stack, since
+     * "gaps" in the stack are filled with entries from the back
+     * (copied).
      */
-    bool IsEmpty() { return GetSize() == 0; }
+    void purge() {
+      unsigned int iStack = 0;
+      unsigned int size = getSize();
+      while (iStack < size) {
+        if (isDeleted(iStack)) {
+          inner_stack_.purge(iStack);
+          indices_.erase(indices_.begin() + iStack);
+        }
+        size = getSize();
+        iStack++;
+      }
+      InnerStackTypeRef::nDeleted_ = 0;
+    }
+
+    std::string as_string() const {
+      std::string str(fmt::format("size {}\n", getSize()));
+      // we make our own begin/end since we want ALL entries
+      std::string new_line = "     ";
+      for (unsigned int iPart = 0; iPart != getSize(); ++iPart) {
+        ConstStackIterator itPart(*this, iPart);
+        str += fmt::format(
+            "{}{}{}", new_line, itPart.as_string(),
+            (inner_stack_.deleted_[GetIndexFromIterator(itPart.GetIndex())] ? " [deleted]"
+                                                                            : ""));
+        new_line = "\n     ";
+      }
+      return str;
+    }
 
   protected:
+    // forward to inner stack
+    // this also checks the allowed bounds of 'i'
+    bool isDeleted(unsigned int i) const {
+      if (i >= indices_.size()) return false;
+      return inner_stack_.isDeleted(GetIndexFromIterator(i + 1));
+    }
+
     /**
-     * We only want to 'see' secondaries indexed in fIndices. In this
+     * We only want to 'see' secondaries indexed in indices_. In this
      * function the conversion form iterator-index to stack-index is
      * performed.
      */
     unsigned int GetIndexFromIterator(const unsigned int vI) const {
-      if (vI == 0) return fProjectileIndex;
-      return fIndices[vI - 1];
+      // this is too much: C8LOG_TRACE("SecondaryView::GetIndexFromIterator({})={}", vI,
+      // (vI?indices_[vI-1]:projectile_index_));
+      if (vI == 0) return projectile_index_;
+      return indices_[vI - 1];
     }
+  };
 
-  private:
-    unsigned int fProjectileIndex;
-    std::vector<unsigned int> fIndices;
+  /**
+   * Class to handle the generation of new secondaries. Used as default mix-in for
+   * SecondaryView.
+   */
+  template <class T1, template <class> class T2>
+  class DefaultSecondaryProducer {
+    using View = SecondaryView<T1, T2, DefaultSecondaryProducer>;
+
+  public:
+    static bool constexpr has_event{false};
+
+    /**
+     * Method is called after a new secondary has been created on the
+     * SecondaryView. Extra logic can be introduced here.
+     *
+     * The input Particle is the new secondary that was produced and
+     * is of course a reference into the SecondaryView itself.
+     */
+    template <typename Particle>
+    auto new_secondary(Particle&&) const {
+      C8LOG_TRACE("DefaultSecondaryProducer::new_secondary(Particle&&)");
+    }
+
+    /**
+     * Method is called when a new SecondaryView is being created
+     * created. Extra logic can be introduced here.
+     *
+     * The input Particle is a reference object into the original
+     * parent stack! It is not a reference into the SecondaryView
+     * itself.
+     */
+    template <typename Particle>
+    DefaultSecondaryProducer(Particle const&) {
+      C8LOG_TRACE("DefaultSecondaryProducer::DefaultSecondaryProducer(Particle&)");
+    }
   };
 
   /*
@@ -236,9 +483,13 @@ namespace corsika::stack {
     generic and universal.
   */
 #if not defined(__clang__) && defined(__GNUC__) || defined(__GNUG__)
-  template <typename S, template <typename> typename _PIType = S::template PIType>
+  template <typename TStack,
+            template <class TStack_, template <class> class MPIType_>
+            class MSecondaryProducer = corsika::stack::DefaultSecondaryProducer,
+            template <typename> typename MPIType_ = TStack::template MPIType>
   struct MakeView {
-    using type = corsika::stack::SecondaryView<typename S::StackImpl, _PIType>;
+    using type = corsika::stack::SecondaryView<typename TStack::StackImpl, MPIType_,
+                                               MSecondaryProducer>;
   };
 #endif
 
diff --git a/Framework/StackInterface/Stack.h b/Framework/StackInterface/Stack.h
index c4ce61945f955365d22d2d115bff175988d228e1..e166fccba8a8acdb957222b08b42bdcf63a87193 100644
--- a/Framework/StackInterface/Stack.h
+++ b/Framework/StackInterface/Stack.h
@@ -8,13 +8,13 @@
 
 #pragma once
 
+#include <corsika/logging/Logging.h>
 #include <corsika/stack/StackIteratorInterface.h>
-// must be after StackIteratorInterface
-#include <corsika/stack/SecondaryView.h>
 #include <corsika/utl/MetaProgramming.h>
 
 #include <stdexcept>
-#include <type_traits>
+#include <string>
+#include <vector>
 
 /**
    All classes around management of particles on a stack.
@@ -51,11 +51,15 @@ namespace corsika::stack {
      loops, ranges, etc.
    */
 
-  template <typename StackDataType, template <typename> typename ParticleInterface>
+  template <typename TStackData, template <typename> typename MParticleInterface>
   class Stack {
-    using StackDataValueType = std::remove_reference_t<StackDataType>;
+    using StackDataValueType = std::remove_reference_t<TStackData>;
 
-    StackDataType fData; ///< this in general holds all the data and can be quite big
+  private:
+    TStackData data_; ///< this in general holds all the data and can be quite big
+    std::vector<bool> deleted_; ///< bit field to flag deleted entries
+  protected:
+    unsigned int nDeleted_ = 0;
 
   private:
     Stack(Stack&) = delete; ///< since Stack can be very big, we don't want to copy it
@@ -64,48 +68,52 @@ namespace corsika::stack {
 
   public:
     /**
-     * if StackDataType is a reference member we *HAVE* to initialize
+     * if TStackData is a reference member we *HAVE* to initialize
      * it in the constructor, this is typically needed for SecondaryView
      */
-    template <typename _ = StackDataType, typename = utl::enable_if<std::is_reference<_>>>
-    Stack(StackDataType vD)
-        : fData(vD) {}
+    template <typename _ = TStackData, typename = utl::enable_if<std::is_reference<_>>>
+    Stack(TStackData vD)
+        : data_(vD)
+        , deleted_(std::vector<bool>(data_.GetSize(), false))
+        , nDeleted_(0) {}
 
     /**
      * This constructor takes any argument and passes it on to the
-     * StackDataType user class. If the user did not provide a suited
+     * TStackData user class. If the user did not provide a suited
      * constructor this will fail with an error message.
      *
      * Furthermore, this is disabled with enable_if for SecondaryView
      * stacks, where the inner data container is always a reference
      * and cannot be initialized here.
      */
-    template <typename... Args, typename _ = StackDataType,
+    template <typename... TArgs, typename _ = TStackData,
               typename = utl::disable_if<std::is_reference<_>>>
-    Stack(Args... args)
-        : fData(args...) {}
+    Stack(TArgs... args)
+        : data_(args...)
+        , deleted_(std::vector<bool>(data_.GetSize(), false))
+        , nDeleted_(0) {}
 
   public:
-    typedef StackDataType
+    typedef TStackData
         StackImpl; ///< this is the type of the user-provided data structure
 
-    template <typename SI>
-    using PIType = ParticleInterface<SI>;
+    template <typename TSI>
+    using MPIType = MParticleInterface<TSI>;
 
     /**
      * Via the StackIteratorInterface and ConstStackIteratorInterface
      * specialization, the type of the StackIterator
      * template class is declared for a particular stack data
      * object. Using CRTP, this also determines the type of
-     * ParticleInterface template class simultaneously.
+     * MParticleInterface template class simultaneously.
      */
     using StackIterator =
-        StackIteratorInterface<StackDataValueType, ParticleInterface, Stack>;
+        StackIteratorInterface<StackDataValueType, MParticleInterface, Stack>;
     using ConstStackIterator =
-        ConstStackIteratorInterface<StackDataValueType, ParticleInterface, Stack>;
+        ConstStackIteratorInterface<StackDataValueType, MParticleInterface, Stack>;
 
     /**
-     * this is the full type of the user-declared ParticleInterface
+     * this is the full type of the user-declared MParticleInterface
      */
     using ParticleInterfaceType = typename StackIterator::ParticleInterfaceType;
     /**
@@ -115,21 +123,33 @@ namespace corsika::stack {
     using ParticleType = StackIterator;
 
     // friends are needed since they need access to protected members
-    friend class StackIteratorInterface<StackDataValueType, ParticleInterface, Stack>;
-    friend class ConstStackIteratorInterface<StackDataValueType, ParticleInterface,
+    friend class StackIteratorInterface<StackDataValueType, MParticleInterface, Stack>;
+    friend class ConstStackIteratorInterface<StackDataValueType, MParticleInterface,
                                              Stack>;
+    template <typename T1, //=TStackData,
+              template <typename>
+              typename M1, //=MParticleInterface,
+                           //             template<typename>typename M2>
+              template <class T2, template <class> class T3> class MSecondaryProducer>
+    friend class SecondaryView; //<TStackData,MParticleInterface,M>; // access for
+                                // SecondaryView
+
+    friend class ParticleBase<StackIterator>;
 
   public:
     /**
-     * @name Most generic proxy methods for StackDataType fData
+     * @name Most generic proxy methods for TStackData data_
      * @{
      */
-    unsigned int GetCapacity() const { return fData.GetCapacity(); }
-    unsigned int GetSize() const { return fData.GetSize(); }
-
-    template <typename... Args>
-    auto Clear(Args... args) {
-      return fData.Clear(args...);
+    unsigned int GetCapacity() const { return data_.GetCapacity(); }
+    unsigned int getDeleted() const { return nDeleted_; }
+    unsigned int getEntries() const { return getSize() - getDeleted(); }
+
+    template <typename... TArgs>
+    void Clear(TArgs... args) {
+      data_.Clear(args...);
+      deleted_ = std::vector<bool>(data_.GetSize(), false);
+      nDeleted_ = 0;
     }
     ///@}
 
@@ -138,60 +158,135 @@ namespace corsika::stack {
      * @name These are functions required by std containers and std loops
      * @{
      */
-    StackIterator begin() { return StackIterator(*this, 0); }
-    StackIterator end() { return StackIterator(*this, GetSize()); }
-    StackIterator last() { return StackIterator(*this, GetSize() - 1); }
+    StackIterator begin() {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!deleted_[i]) break;
+      }
+      return StackIterator(*this, i);
+    }
+    StackIterator end() { return StackIterator(*this, getSize()); }
+    StackIterator last() {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!deleted_[getSize() - 1 - i]) break;
+      }
+      return StackIterator(*this, getSize() - 1 - i);
+    }
 
-    ConstStackIterator begin() const { return ConstStackIterator(*this, 0); }
-    ConstStackIterator end() const { return ConstStackIterator(*this, GetSize()); }
-    ConstStackIterator last() const { return ConstStackIterator(*this, GetSize() - 1); }
+    ConstStackIterator begin() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!deleted_[i]) break;
+      }
+      return ConstStackIterator(*this, i);
+    }
+    ConstStackIterator end() const { return ConstStackIterator(*this, getSize()); }
+    ConstStackIterator last() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!deleted_[getSize() - 1 - i]) break;
+      }
+      return ConstStackIterator(*this, getSize() - 1 - i);
+    }
 
-    ConstStackIterator cbegin() const { return ConstStackIterator(*this, 0); }
-    ConstStackIterator cend() const { return ConstStackIterator(*this, GetSize()); }
-    ConstStackIterator clast() const { return ConstStackIterator(*this, GetSize() - 1); }
+    ConstStackIterator cbegin() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!deleted_[i]) break;
+      }
+      return ConstStackIterator(*this, i);
+    }
+    ConstStackIterator cend() const { return ConstStackIterator(*this, getSize()); }
+    ConstStackIterator clast() const {
+      unsigned int i = 0;
+      for (; i < getSize(); ++i) {
+        if (!deleted_[getSize() - 1 - i]) break;
+      }
+      return ConstStackIterator(*this, getSize() - 1 - i);
+    }
+    StackIterator at(unsigned int i) { return StackIterator(*this, i); }
+    ConstStackIterator at(unsigned int i) const { return ConstStackIterator(*this, i); }
+    StackIterator first() { return StackIterator{*this, 0}; }
+    ConstStackIterator cfirst() const { return ConstStackIterator{*this, 0}; }
     /// @}
 
+    StackIterator GetNextParticle() {
+      while (purgeLastIfDeleted()) {}
+      return last();
+    }
+
     /**
      * increase stack size, create new particle at end of stack
      */
-    template <typename... Args>
-    StackIterator AddParticle(const Args... v) {
-      fData.IncrementSize();
-      return StackIterator(*this, GetSize() - 1, v...);
+    template <typename... TArgs>
+    StackIterator AddParticle(const TArgs... v) {
+      C8LOG_TRACE("Stack::AddParticle");
+      data_.IncrementSize();
+      deleted_.push_back(false);
+      return StackIterator(*this, getSize() - 1, v...);
     }
 
+  protected:
     /**
      * increase stack size, create new particle at end of stack, related to parent
      * particle/projectile
+     *
+     * This should only get internally called from a
+     * StackIterator::AddSecondary via ParticleBase
      */
-    template <typename... Args>
-    StackIterator AddSecondary(StackIterator& parent, const Args... v) {
-      fData.IncrementSize();
-      return StackIterator(*this, GetSize() - 1, parent, v...);
+    template <typename... TArgs>
+    StackIterator AddSecondary(StackIterator& parent, const TArgs... v) {
+      C8LOG_TRACE("Stack::AddSecondary");
+      data_.IncrementSize();
+      deleted_.push_back(false);
+      return StackIterator(*this, getSize() - 1, parent, v...);
     }
 
+  public:
     void Swap(StackIterator a, StackIterator b) {
-      fData.Swap(a.GetIndex(), b.GetIndex());
-    }
-    void Swap(ConstStackIterator a, ConstStackIterator b) {
-      fData.Swap(a.GetIndex(), b.GetIndex());
+      C8LOG_TRACE("Stack::Swap");
+      Swap(a.GetIndex(), b.GetIndex());
     }
     void Copy(StackIterator a, StackIterator b) {
-      fData.Copy(a.GetIndex(), b.GetIndex());
+      C8LOG_TRACE("Stack::Copy");
+      Copy(a.GetIndex(), b.GetIndex());
     }
     void Copy(ConstStackIterator a, StackIterator b) {
-      fData.Copy(a.GetIndex(), b.GetIndex());
+      C8LOG_TRACE("Stack::Copy");
+      data_.Copy(a.GetIndex(), b.GetIndex());
+      if (deleted_[b.GetIndex()] && !deleted_[a.GetIndex()]) nDeleted_--;
+      if (!deleted_[b.GetIndex()] && deleted_[a.GetIndex()]) nDeleted_++;
+      deleted_[b.GetIndex()] = deleted_[a.GetIndex()];
+    }
+
+  protected:
+    void Swap(unsigned int a, unsigned int b) {
+      C8LOG_TRACE("Stack::Swap(unsigned int)");
+      data_.Swap(a, b);
+      std::swap(deleted_[a], deleted_[b]);
+    }
+    void Copy(unsigned int a, unsigned int b) {
+      C8LOG_TRACE("Stack::Copy");
+      data_.Copy(a, b);
+      if (deleted_[b] && !deleted_[a]) nDeleted_--;
+      if (!deleted_[b] && deleted_[a]) nDeleted_++;
+      deleted_[b] = deleted_[a];
     }
 
     /**
      * delete this particle
      */
+  public:
     void Delete(StackIterator p) {
-      if (GetSize() == 0) { /*error*/
+      C8LOG_TRACE("Stack::Delete");
+      if (IsEmpty()) { /*error*/
         throw std::runtime_error("Stack, cannot delete entry since size is zero");
       }
-      if (p.GetIndex() < GetSize() - 1) fData.Copy(GetSize() - 1, p.GetIndex());
-      DeleteLast();
+      if (deleted_[p.GetIndex()]) { /*error*/
+        throw std::runtime_error("Stack, cannot delete entry since already deleted");
+      }
+      Delete(p.GetIndex());
     }
     /**
      * delete this particle
@@ -199,35 +294,116 @@ namespace corsika::stack {
     void Delete(ParticleInterfaceType p) { Delete(p.GetIterator()); }
 
     /**
-     * delete last particle on stack by decrementing stack size
+     * check if there are no further non-deleted particles on stack
+     */
+    bool IsEmpty() { return getEntries() == 0; }
+
+    /**
+     * check if this particle was already deleted
      */
-    void DeleteLast() { fData.DecrementSize(); }
+    bool isDeleted(const StackIterator& p) { return isDeleted(p.GetIndex()); }
+    bool isDeleted(const ConstStackIterator& p) const { return isDeleted(p.GetIndex()); }
+    bool isDeleted(const ParticleInterfaceType& p) { return isDeleted(p.GetIterator()); }
 
     /**
-     * check if there are no further particles on stack
+     * Function to ultimatively remove the last entry from the stack,
+     * if it was marked as deleted before. If this is not the case,
+     * the function will just return false and do nothing.
      */
-    bool IsEmpty() { return GetSize() == 0; }
+    bool purgeLastIfDeleted() {
+      if (!deleted_.back())
+        return false; // the last particle is not marked for deletion. Do nothing.
+      C8LOG_TRACE("Stack::purgeLastIfDeleted: yes");
+      data_.DecrementSize();
+      nDeleted_--;
+      deleted_.pop_back();
+      return true;
+    }
 
     /**
-     * return next particle from stack
+     * Function to ultimatively remove all entries from the stack
+     * marked as deleted.
+     *
+     * Careful: this will re-order the entries on the stack, since
+     * "gaps" in the stack are filled with entries from the back
+     * (copied).
      */
-    StackIterator GetNextParticle() { return last(); }
+    void purge() {
+      unsigned int iStackFront = 0;
+      unsigned int iStackBack = getSize() - 1;
+      for (unsigned int iDeleted = 0; iDeleted < getDeleted(); ++iDeleted) {
+        // search first delete entry on stack
+        while (!deleted_[iStackFront]) { iStackFront++; }
+        // search for last non-deleted particle on stack
+        while (deleted_[iStackBack]) { iStackBack--; }
+        // copy entry from iStackBack to iStackFront
+        data_.Copy(iStackBack, iStackFront);
+        data_.DecrementSize();
+      }
+      deleted_.clear();
+      nDeleted_ = 0;
+    }
+
+    unsigned int getSize() const { return data_.GetSize(); }
+
+    std::string as_string() const {
+      std::string str(fmt::format("size {}, entries {}, deleted {} \n", getSize(),
+                                  getEntries(), getDeleted()));
+      // we make our own begin/end since we want ALL entries
+      std::string new_line = "     ";
+      for (unsigned int iPart = 0; iPart != getSize(); ++iPart) {
+        ConstStackIterator itPart(*this, iPart);
+        str += fmt::format("{}{}{}", new_line, itPart.as_string(),
+                           (deleted_[itPart.GetIndex()] ? " [deleted]" : ""));
+        new_line = "\n     ";
+      }
+      return str;
+    }
 
   protected:
+    bool isDeleted(unsigned int i) const {
+      if (i >= deleted_.size()) return false;
+      return deleted_.at(i);
+    }
+
+    void Delete(unsigned int i) {
+      deleted_[i] = true;
+      nDeleted_++;
+    }
+
+    /**
+     * will remove from storage the element i. This is a helper
+     * function for SecondaryView.
+     */
+    void purge(unsigned int i) {
+      unsigned int iStackBack = getSize() - 1;
+      // search for last non-deleted particle on stack
+      while (deleted_[iStackBack]) { iStackBack--; }
+      // copy entry from iStackBack to iStackFront
+      data_.Copy(iStackBack, i);
+      if (deleted_[i]) nDeleted_--;
+      deleted_[i] = deleted_[iStackBack];
+      data_.DecrementSize();
+      deleted_.pop_back();
+    }
+
     /**
      * Function to perform eventual transformation from
      * StackIterator::GetIndex() to index in data stored in
-     * StackDataType fData. By default (and in almost all cases) this
+     * TStackData data_. By default (and in almost all cases) this
      * should just be identiy. See class SecondaryView for an alternative implementation.
      */
-    unsigned int GetIndexFromIterator(const unsigned int vI) const { return vI; }
+    unsigned int GetIndexFromIterator(const unsigned int vI) const {
+      // this is too much: C8LOG_TRACE("Stack::GetIndexFromIterator({})={}", vI, vI);
+      return vI;
+    }
 
     /**
-     * @name Return reference to StackDataType object fData for data access
+     * @name Return reference to TStackData object data_ for data access
      * @{
      */
-    StackDataValueType& GetStackData() { return fData; }
-    const StackDataValueType& GetStackData() const { return fData; }
+    StackDataValueType& GetStackData() { return data_; }
+    const StackDataValueType& GetStackData() const { return data_; }
     ///@}
   };
 
diff --git a/Framework/StackInterface/StackIteratorInterface.h b/Framework/StackInterface/StackIteratorInterface.h
index 5213e30d4cead0cf5f2170f3a70cdc03c2974e3a..2229c17bc395a2f12b83aae0ae5c89162c95fdd3 100644
--- a/Framework/StackInterface/StackIteratorInterface.h
+++ b/Framework/StackInterface/StackIteratorInterface.h
@@ -10,14 +10,24 @@
 
 #include <corsika/stack/ParticleBase.h>
 
+namespace corsika::history {
+  template <typename T, template <typename> typename ParticleInterface>
+  class HistorySecondaryProducer; // forward decl.
+}
+
 namespace corsika::stack {
 
-  template <typename StackDataType, template <typename> typename ParticleInterface>
+  template <typename TStackData, template <typename> typename TParticleInterface>
   class Stack; // forward decl
 
-  template <typename StackDataType, template <typename> typename ParticleInterface>
+  template <typename TStackData, template <typename> typename TParticleInterface,
+            template <class T1, template <class> class T2> class MSecondaryProducer>
   class SecondaryView; // forward decl
 
+  template <typename TStackData, template <typename> typename TParticleInterface,
+            typename StackType>
+  class ConstStackIteratorInterface; // forward decl
+
   /**
      @class StackIteratorInterface
 
@@ -34,51 +44,61 @@ namespace corsika::stack {
      The template argument Stack determines the type of Stack object
      the data is stored in. A pointer to the Stack object is part of
      the StackIteratorInterface. In addition to Stack the iterator only knows
-     the index fIndex in the Stack data.
+     the index index_ in the Stack data.
 
-     The template argument `ParticleInterface` acts as a policy to provide
-     readout function of Particle data from the stack. The ParticleInterface
+     The template argument `TParticleInterface` acts as a policy to provide
+     readout function of Particle data from the stack. The TParticleInterface
      class must know how to retrieve information from the Stack data
-     for a particle entry at any index fIndex.
+     for a particle entry at any index index_.
 
-     The ParticleInterface class must be written and provided by the
+     The TParticleInterface class must be written and provided by the
      user, it contains methods like <code> auto GetData() const {
      return GetStackData().GetData(GetIndex()); }</code>, where
      StackIteratorInterface::GetStackData() return a reference to the
-     object storing the particle data of type StackDataType. And
+     object storing the particle data of type TStackData. And
      StackIteratorInterface::GetIndex() provides the iterator index to
-     be readout. The StackDataType is another user-provided class to
+     be readout. The TStackData is another user-provided class to
      store data and must implement functions compatible with
-     ParticleInterface, in this example StackDataType::GetData(const unsigned int
+     TParticleInterface, in this example TStackData::GetData(const unsigned int
      vIndex).
 
      For two examples see stack_example.cc, or the
      corsika::processes::sibyll::SibStack class
   */
 
-  template <typename StackDataType, template <typename> typename ParticleInterface,
-            typename StackType = Stack<StackDataType, ParticleInterface>>
+  template <typename TStackData, template <typename> typename TParticleInterface,
+            typename StackType = Stack<TStackData, TParticleInterface>>
   class StackIteratorInterface
-      : public ParticleInterface<
-            StackIteratorInterface<StackDataType, ParticleInterface, StackType>> {
+      : public TParticleInterface<
+            StackIteratorInterface<TStackData, TParticleInterface, StackType>> {
 
   public:
     using ParticleInterfaceType =
-        ParticleInterface<corsika::stack::StackIteratorInterface<
-            StackDataType, ParticleInterface, StackType>>;
+        TParticleInterface<corsika::stack::StackIteratorInterface<
+            TStackData, TParticleInterface, StackType>>;
 
     // friends are needed for access to protected methods
-    friend class Stack<StackDataType,
-                       ParticleInterface>; // for access to GetIndex for Stack
-    friend class Stack<StackDataType&, ParticleInterface>; // for access to GetIndex
-                                                           // SecondaryView : public Stack
-    friend class ParticleBase<StackIteratorInterface>; // for access to GetStackDataType
-    friend class SecondaryView<StackDataType,
-                               ParticleInterface>; // access for SecondaryView
+    friend class Stack<TStackData,
+                       TParticleInterface>; // for access to GetIndex for Stack
+    friend class Stack<TStackData&, TParticleInterface>; // for access to GetIndex
+                                                         // SecondaryView : public Stack
+    friend class ParticleBase<StackIteratorInterface>;   // for access to GetStackData
+
+    template <typename T1,                     // best fix this to: TStackData,
+              template <typename> typename M1, // best fix this to: TParticleInterface,
+              template <typename T, template <typename> typename T3> typename M2>
+    friend class SecondaryView; // access grant for SecondaryView
+
+    template <typename T, template <typename> typename ParticleInterface>
+    friend class corsika::history::HistorySecondaryProducer;
+
+    friend class ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>;
+
+  protected:
+    unsigned int index_ = 0;
 
   private:
-    unsigned int fIndex = 0;
-    StackType* fData = 0; // info: Particles and StackIterators become invalid when parent
+    StackType* data_ = 0; // info: Particles and StackIterators become invalid when parent
                           // Stack is copied or deleted!
 
     // it is not allowed to create a "dangling" stack iterator
@@ -86,12 +106,12 @@ namespace corsika::stack {
 
   public:
     StackIteratorInterface(StackIteratorInterface const& vR)
-        : fIndex(vR.fIndex)
-        , fData(vR.fData) {}
+        : index_(vR.index_)
+        , data_(vR.data_) {}
 
     StackIteratorInterface& operator=(StackIteratorInterface const& vR) {
-      fIndex = vR.fIndex;
-      fData = vR.fData;
+      index_ = vR.index_;
+      data_ = vR.data_;
       return *this;
     }
 
@@ -100,8 +120,8 @@ namespace corsika::stack {
           @param index index on stack
        */
     StackIteratorInterface(StackType& data, const unsigned int index)
-        : fIndex(index)
-        , fData(&data) {}
+        : index_(index)
+        , data_(&data) {}
 
     /** constructor that also sets new values on particle data object
         @param data reference to the stack [rw]
@@ -112,8 +132,8 @@ namespace corsika::stack {
      */
     template <typename... Args>
     StackIteratorInterface(StackType& data, const unsigned int index, const Args... args)
-        : fIndex(index)
-        , fData(&data) {
+        : index_(index)
+        , data_(&data) {
       (**this).SetParticleData(args...);
     }
 
@@ -130,29 +150,47 @@ namespace corsika::stack {
     template <typename... Args>
     StackIteratorInterface(StackType& data, const unsigned int index,
                            StackIteratorInterface& parent, const Args... args)
-        : fIndex(index)
-        , fData(&data) {
+        : index_(index)
+        , data_(&data) {
       (**this).SetParticleData(*parent, args...);
     }
 
+    bool isDeleted() const { return GetStack().isDeleted(*this); }
+
   public:
     /** @name Iterator interface
         @{
     */
     StackIteratorInterface& operator++() {
-      ++fIndex;
+      do {
+        ++index_;
+      } while (
+          GetStack().isDeleted(*this)); // this also check the allowed bounds of index_
       return *this;
     }
     StackIteratorInterface operator++(int) {
       StackIteratorInterface tmp(*this);
-      ++fIndex;
+      do {
+        ++index_;
+      } while (
+          GetStack().isDeleted(*this)); // this also check the allowed bounds of index_
       return tmp;
     }
-    StackIteratorInterface operator+(int delta) {
-      return StackIteratorInterface(*fData, fIndex + delta);
+    StackIteratorInterface operator+(int delta) const {
+      return StackIteratorInterface(*data_, index_ + delta);
+    }
+    bool operator==(const StackIteratorInterface& rhs) const {
+      return index_ == rhs.index_;
     }
-    bool operator==(const StackIteratorInterface& rhs) { return fIndex == rhs.fIndex; }
-    bool operator!=(const StackIteratorInterface& rhs) { return fIndex != rhs.fIndex; }
+    bool operator!=(const StackIteratorInterface& rhs) const {
+      return index_ != rhs.index_;
+    }
+    bool operator==(
+        const ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>& rhs)
+        const; // implement below
+    bool operator!=(
+        const ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>& rhs)
+        const; // implement below
 
     /**
      * Convert iterator to value type, where value type is the user-provided particle
@@ -176,18 +214,18 @@ namespace corsika::stack {
      * @{
      */
     /// Get current particle index
-    inline unsigned int GetIndex() const { return fIndex; }
+    inline unsigned int GetIndex() const { return index_; }
     /// Get current particle Stack object
-    inline StackType& GetStack() { return *fData; }
+    inline StackType& GetStack() { return *data_; }
     /// Get current particle const Stack object
-    inline const StackType& GetStack() const { return *fData; }
-    /// Get current user particle StackDataType object
-    inline StackDataType& GetStackData() { return fData->GetStackData(); }
-    /// Get current const user particle StackDataType object
-    inline const StackDataType& GetStackData() const { return fData->GetStackData(); }
+    inline const StackType& GetStack() const { return *data_; }
+    /// Get current user particle TStackData object
+    inline TStackData& GetStackData() { return data_->GetStackData(); }
+    /// Get current const user particle TStackData object
+    inline const TStackData& GetStackData() const { return data_->GetStackData(); }
     /// Get data index as mapped in Stack class
     inline unsigned int GetIndexFromIterator() const {
-      return fData->GetIndexFromIterator(fIndex);
+      return data_->GetIndexFromIterator(index_);
     }
     ///@}
   }; // end class StackIterator
@@ -198,24 +236,39 @@ namespace corsika::stack {
      This is the iterator class for const-access to stack data
    */
 
-  template <typename StackDataType, template <typename> typename ParticleInterface,
-            typename StackType = Stack<StackDataType, ParticleInterface>>
+  template <typename TStackData, template <typename> typename TParticleInterface,
+            typename StackType = Stack<TStackData, TParticleInterface>>
   class ConstStackIteratorInterface
-      : public ParticleInterface<
-            ConstStackIteratorInterface<StackDataType, ParticleInterface, StackType>> {
+      : public TParticleInterface<
+            ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>> {
 
   public:
-    typedef ParticleInterface<
-        ConstStackIteratorInterface<StackDataType, ParticleInterface, StackType>>
+    typedef TParticleInterface<
+        ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>>
         ParticleInterfaceType;
 
-    friend class Stack<StackDataType, ParticleInterface>;   // for access to GetIndex
-    friend class ParticleBase<ConstStackIteratorInterface>; // for access to
-                                                            // GetStackDataType
+    // friends are needed for access to protected methods
+    friend class Stack<TStackData,
+                       TParticleInterface>; // for access to GetIndex for Stack
+    friend class Stack<TStackData&, TParticleInterface>; // for access to GetIndex
+
+    friend class ParticleBase<ConstStackIteratorInterface>; // for access to GetStackData
+
+    template <typename T1,                     // best fix to: TStackData,
+              template <typename> typename M1, // best fix to: TParticleInterface,
+              template <class T2, template <class> class T3> class MSecondaryProducer>
+    friend class SecondaryView; // access for SecondaryView
+
+    friend class StackIteratorInterface<TStackData, TParticleInterface, StackType>;
+
+    template <typename T, template <typename> typename ParticleInterface>
+    friend class corsika::history::HistorySecondaryProducer;
+
+  protected:
+    unsigned int index_ = 0;
 
   private:
-    unsigned int fIndex = 0;
-    const StackType* fData = 0; // info: Particles and StackIterators become invalid when
+    const StackType* data_ = 0; // info: Particles and StackIterators become invalid when
                                 // parent Stack is copied or deleted!
 
     // we don't want to allow dangling iterators to exist
@@ -223,8 +276,8 @@ namespace corsika::stack {
 
   public:
     ConstStackIteratorInterface(const StackType& data, const unsigned int index)
-        : fIndex(index)
-        , fData(&data) {}
+        : index_(index)
+        , data_(&data) {}
 
     /**
        @class ConstStackIteratorInterface
@@ -239,27 +292,43 @@ namespace corsika::stack {
        See documentation of StackIteratorInterface for more details.
     */
 
+    bool isDeleted() const { return GetStack().isDeleted(*this); }
+
   public:
     /** @name Iterator interface
      */
     ///@{
     ConstStackIteratorInterface& operator++() {
-      ++fIndex;
+      do {
+        ++index_;
+      } while (
+          GetStack().isDeleted(*this)); // this also check the allowed bounds of index_
       return *this;
     }
     ConstStackIteratorInterface operator++(int) {
       ConstStackIteratorInterface tmp(*this);
-      ++fIndex;
+      do {
+        ++index_;
+      } while (
+          GetStack().isDeleted(*this)); // this also check the allowed bounds of index_
       return tmp;
     }
-    ConstStackIteratorInterface operator+(int delta) {
-      return ConstStackIteratorInterface(*fData, fIndex + delta);
+    ConstStackIteratorInterface operator+(const int delta) const {
+      return ConstStackIteratorInterface(*data_, index_ + delta);
+    }
+    bool operator==(const ConstStackIteratorInterface& rhs) const {
+      return index_ == rhs.index_;
     }
-    bool operator==(const ConstStackIteratorInterface& rhs) {
-      return fIndex == rhs.fIndex;
+    bool operator!=(const ConstStackIteratorInterface& rhs) const {
+      return index_ != rhs.index_;
     }
-    bool operator!=(const ConstStackIteratorInterface& rhs) {
-      return fIndex != rhs.fIndex;
+    bool operator==(const StackIteratorInterface<TStackData, TParticleInterface,
+                                                 StackType>& rhs) const {
+      return index_ == rhs.index_;
+    }
+    bool operator!=(const StackIteratorInterface<TStackData, TParticleInterface,
+                                                 StackType>& rhs) const {
+      return index_ != rhs.index_;
     }
 
     const ParticleInterfaceType& operator*() const {
@@ -272,14 +341,30 @@ namespace corsika::stack {
         Only the const versions for read-only access
      */
     ///@{
-    inline unsigned int GetIndex() const { return fIndex; }
-    inline const StackType& GetStack() const { return *fData; }
-    inline const StackDataType& GetStackData() const { return fData->GetStackData(); }
+    inline unsigned int GetIndex() const { return index_; }
+    inline const StackType& GetStack() const { return *data_; }
+    inline const TStackData& GetStackData() const { return data_->GetStackData(); }
     /// Get data index as mapped in Stack class
     inline unsigned int GetIndexFromIterator() const {
-      return fData->GetIndexFromIterator(fIndex);
+      return data_->GetIndexFromIterator(index_);
     }
     ///@}
   }; // end class ConstStackIterator
 
+  template <typename TStackData, template <typename> typename TParticleInterface,
+            typename StackType>
+  bool StackIteratorInterface<TStackData, TParticleInterface, StackType>::operator==(
+      const ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>& rhs)
+      const {
+    return index_ == rhs.index_;
+  }
+
+  template <typename TStackData, template <typename> typename TParticleInterface,
+            typename StackType>
+  bool StackIteratorInterface<TStackData, TParticleInterface, StackType>::operator!=(
+      const ConstStackIteratorInterface<TStackData, TParticleInterface, StackType>& rhs)
+      const {
+    return index_ != rhs.index_;
+  }
+
 } // namespace corsika::stack
diff --git a/Framework/StackInterface/testCombinedStack.cc b/Framework/StackInterface/testCombinedStack.cc
index 6f4a9c17fe216133be861149887f87e7dd235a1d..9e4d9274f256544a8ea12e0541a2f87b3d130955 100644
--- a/Framework/StackInterface/testCombinedStack.cc
+++ b/Framework/StackInterface/testCombinedStack.cc
@@ -6,6 +6,8 @@
  * the license.
  */
 
+#define protected public // to also test the internal state of objects
+
 #include <corsika/stack/CombinedStack.h>
 #include <corsika/stack/SecondaryView.h>
 #include <corsika/stack/Stack.h>
@@ -14,7 +16,6 @@
 // test-build, and inluce file is obtained from CMAKE_CURRENT_SOURCE_DIR
 
 #include <iomanip>
-#include <iostream>
 #include <vector>
 
 #include <catch2/catch.hpp>
@@ -107,7 +108,7 @@ TEST_CASE("Combined Stack", "[stack]") {
     s.AddParticle(std::tuple{0.});
     s.Copy(s.cbegin(), s.begin());
     s.Swap(s.begin(), s.begin());
-    REQUIRE(s.GetSize() == 1);
+    CHECK(s.getSize() == 1);
   }
 
   SECTION("construct") {
@@ -120,72 +121,99 @@ TEST_CASE("Combined Stack", "[stack]") {
 
     StackTest s;
     s.AddParticle(std::tuple{9.9});
-    REQUIRE(sum2(s) == 0.);
-    REQUIRE(sum(s) == 9.9);
+    CHECK(sum2(s) == 0.);
+    CHECK(sum(s) == 9.9);
   }
 
   SECTION("delete from stack") {
 
     StackTest s;
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 0);
     StackTest::StackIterator p =
         s.AddParticle(std::tuple{0.}); // valid way to access particle data
     p.SetData(8.9);
     p.SetData2(3.);
-    REQUIRE(sum2(s) == 3.);
-    REQUIRE(sum(s) == 8.9);
-    REQUIRE(s.GetSize() == 1);
+    CHECK(sum2(s) == 3.);
+    CHECK(sum(s) == 8.9);
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 1);
     s.Delete(p);
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 0);
   }
 
   SECTION("delete particle") {
 
     StackTest s;
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 0);
     auto p = s.AddParticle(
         std::tuple{9.9}); // also valid way to access particle data, identical to above
-    REQUIRE(s.GetSize() == 1);
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 1);
     p.Delete();
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 0);
   }
 
   SECTION("create secondaries") {
     StackTest s;
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 0);
     auto iter = s.AddParticle(std::tuple{9.9});
     iter.SetData2(2);
-    REQUIRE(s.GetSize() == 1);
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 1);
     iter.AddSecondary(std::tuple{4.4});
-    REQUIRE(s.GetSize() == 2);
+    CHECK(s.getSize() == 2);
+    CHECK(s.getEntries() == 2);
     // p.AddSecondary(3.3, 2.2, 1.);
-    // REQUIRE(s.GetSize() == 3);
+    // CHECK(s.getSize() == 3);
     double v = 0;
     for (const auto& i : s) {
       v += i.GetData();
-      REQUIRE(i.GetData2() == 2);
+      CHECK(i.GetData2() == 2);
     }
-    REQUIRE(v == 9.9 + 4.4);
+    CHECK(v == 9.9 + 4.4);
   }
 
   SECTION("get next particle") {
     StackTest s;
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 0);
+    CHECK(s.getEntries() == 0);
+    CHECK(s.IsEmpty());
+
     auto p1 = s.AddParticle(std::tuple{9.9});
     auto p2 = s.AddParticle(std::tuple{8.8});
     p1.SetData2(20.2);
     p2.SetData2(20.3);
-    auto particle = s.GetNextParticle(); // first particle
-    REQUIRE(particle.GetData() == 8.8);
-    REQUIRE(particle.GetData2() == 20.3);
+    CHECK(s.getSize() == 2);
+    CHECK(s.getEntries() == 2);
+    CHECK(!s.IsEmpty());
 
-    particle.Delete();
+    auto particle = s.GetNextParticle(); // first particle
+    CHECK(particle.GetData() == 8.8);
+    CHECK(particle.GetData2() == 20.3);
+
+    particle.Delete(); // only marks (last) particle as "deleted"
+    CHECK(s.getSize() == 2);
+    CHECK(s.getEntries() == 1);
+    CHECK(!s.IsEmpty());
+
+    /*
+      This following call to GetNextParticle will realize that the
+      current last particle on the stack was marked "deleted" and will
+      purge it: stack size is reduced by one.
+     */
     auto particle2 = s.GetNextParticle(); // first particle
-    REQUIRE(particle2.GetData() == 9.9);
-    REQUIRE(particle2.GetData2() == 20.2);
-    particle2.Delete();
-
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 1);
+    CHECK(!s.IsEmpty());
+    CHECK(particle2.GetData() == 9.9);
+    CHECK(particle2.GetData2() == 20.2);
+
+    particle2.Delete(); // also mark this particle as "deleted"
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 0);
+    CHECK(s.IsEmpty());
   }
 }
 
@@ -199,7 +227,7 @@ class TestStackData3 {
 public:
   // these functions are needed for the Stack interface
   void Clear() { fData3.clear(); }
-  unsigned int GetSize() const { return fData3.size(); }
+  unsigned int getSize() const { return fData3.size(); }
   unsigned int GetCapacity() const { return fData3.size(); }
   void Copy(const int i1, const int i2) { fData3[i2] = fData3[i1]; }
   void Swap(const int i1, const int i2) {
@@ -248,7 +276,7 @@ public:
 // combined stack
 template <typename StackIter>
 using CombinedTestInterfaceType2 =
-    corsika::stack::CombinedParticleInterface<StackTest::PIType, TestParticleInterface3,
+    corsika::stack::CombinedParticleInterface<StackTest::MPIType, TestParticleInterface3,
                                               StackIter>;
 
 using StackTest2 = CombinedStack<typename StackTest::StackImpl, TestStackData3,
@@ -259,11 +287,17 @@ TEST_CASE("Combined Stack - multi", "[stack]") {
   SECTION("create secondaries") {
 
     StackTest2 s;
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 0);
+    CHECK(s.IsEmpty()); // size = entries = 0
+
     // add new particle, only provide tuple data for StackTest
     auto p1 = s.AddParticle(std::tuple{9.9});
     // add new particle, provide tuple data for both StackTest and TestStackData3
     auto p2 = s.AddParticle(std::tuple{8.8}, std::tuple{0.1});
+
+    CHECK(s.getSize() == 2);
+    CHECK(!s.IsEmpty()); // size = entries = 2
+
     // examples to explicitly change data on stack
     p2.SetData2(0.1); // not clear why this is needed, need to check
                       // SetParticleData workflow for more complicated
@@ -271,32 +305,46 @@ TEST_CASE("Combined Stack - multi", "[stack]") {
     p1.SetData3(20.2);
     p2.SetData3(10.3);
 
-    REQUIRE(p1.GetData() == 9.9);
-    REQUIRE(p1.GetData2() == 0.);
+    CHECK(p1.GetData() == 9.9);
+    CHECK(p1.GetData2() == 0.);
     p1.SetData2(10.2);
-    REQUIRE(p1.GetData2() == 10.2);
-    REQUIRE(p1.GetData3() == 20.2);
+    CHECK(p1.GetData2() == 10.2);
+    CHECK(p1.GetData3() == 20.2);
 
-    REQUIRE(p2.GetData() == 8.8);
-    REQUIRE(p2.GetData2() == 0.1);
-    REQUIRE(p2.GetData3() == 10.3);
+    CHECK(p2.GetData() == 8.8);
+    CHECK(p2.GetData2() == 0.1);
+    CHECK(p2.GetData3() == 10.3);
 
     auto particle = s.GetNextParticle(); // first particle
-    REQUIRE(particle.GetData() == 8.8);
-    REQUIRE(particle.GetData2() == 0.1);
-    REQUIRE(particle.GetData3() == 10.3);
+    CHECK(particle.GetData() == 8.8);
+    CHECK(particle.GetData2() == 0.1);
+    CHECK(particle.GetData3() == 10.3);
 
-    REQUIRE(s.GetSize() == 2);
     auto sec = particle.AddSecondary(std::tuple{4.4});
-    REQUIRE(s.GetSize() == 3);
-    REQUIRE(sec.GetData() == 4.4);
-    REQUIRE(sec.GetData2() == 0.1);
-    REQUIRE(sec.GetData3() == 10.3);
-
-    sec.Delete();
-    s.DeleteLast();
-    s.GetNextParticle().Delete();
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getSize() == 3);
+    CHECK(s.getEntries() == 3);
+    CHECK(sec.GetData() == 4.4);
+    CHECK(sec.GetData2() == 0.1);
+    CHECK(sec.GetData3() == 10.3);
+
+    sec.Delete(); // mark for deletion: size=3, entries=2
+    CHECK(s.getSize() == 3);
+    CHECK(s.getEntries() == 2);
+    CHECK(!s.IsEmpty());
+
+    s.last().Delete(); // mark for deletion: size=3, entries=1
+    CHECK(s.getSize() == 3);
+    CHECK(s.getEntries() == 1);
+    CHECK(!s.IsEmpty());
+
+    /*
+       GetNextParticle will find two entries marked as "deleted" and
+       will purge this from the end of the stack: size = 1
+    */
+    s.GetNextParticle().Delete(); // mark for deletion: size=3, entries=0
+    CHECK(s.getSize() == 1);
+    CHECK(s.getEntries() == 0);
+    CHECK(s.IsEmpty());
   }
 }
 
@@ -314,7 +362,7 @@ TEST_CASE("Combined Stack - multi", "[stack]") {
  */
 template <typename StackIter>
 using CombinedTestInterfaceType2 =
-    corsika::stack::CombinedParticleInterface<StackTest::PIType, TestParticleInterface3,
+    corsika::stack::CombinedParticleInterface<StackTest::MPIType, TestParticleInterface3,
                                               StackIter>;
 
 using StackTest2 = CombinedStack<typename StackTest::StackImpl, TestStackData3,
@@ -335,12 +383,11 @@ TEST_CASE("Combined Stack - secondary view") {
 
     StackTest2 stack;
     auto particle = stack.AddParticle(std::tuple{9.9});
-    // cout << boost::typeindex::type_id_runtime(particle).pretty_name() << endl;
     StackTestView view(particle);
 
     auto projectile = view.GetProjectile();
     projectile.AddSecondary(std::tuple{8.8});
 
-    REQUIRE(stack.GetSize() == 2);
+    CHECK(stack.getSize() == 2);
   }
 }
diff --git a/Framework/StackInterface/testSecondaryView.cc b/Framework/StackInterface/testSecondaryView.cc
index 356a0bd5555b8a9274dc206cd1da064153764dc8..9cd1e46667e08696e81bc44d25d79b6a634500d3 100644
--- a/Framework/StackInterface/testSecondaryView.cc
+++ b/Framework/StackInterface/testSecondaryView.cc
@@ -6,6 +6,8 @@
  * the license.
  */
 
+#define protected public // to also test the internal state of objects
+
 #include <corsika/stack/SecondaryView.h>
 #include <corsika/stack/Stack.h>
 
@@ -13,7 +15,6 @@
 // test-build, and inluce file is obtained from CMAKE_CURRENT_SOURCE_DIR
 
 #include <iomanip>
-#include <iostream>
 #include <vector>
 
 #include <catch2/catch.hpp>
@@ -42,88 +43,113 @@ using Particle = typename StackTest::ParticleType;
 
 TEST_CASE("SecondaryStack", "[stack]") {
 
+  logging::SetLevel(logging::level::debug);
+
   // helper function for sum over stack data
   auto sum = [](const StackTest& stack) {
     double v = 0;
-    for (const auto& p : stack) v += p.GetData();
+    for (const auto& p : stack) { v += p.GetData(); }
     return v;
   };
 
+  auto sumView = [](const StackTestView& stack) {
+    double value = 0;
+    for (const auto& p : stack) { value += p.GetData(); }
+    return value;
+  };
+
   SECTION("secondary view") {
-    StackTest s;
-    REQUIRE(s.GetSize() == 0);
-    s.AddParticle(std::tuple{9.9});
-    s.AddParticle(std::tuple{8.8});
-    const double sumS = 9.9 + 8.8;
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    CHECK(stack.IsEmpty());
+
+    stack.AddParticle(std::tuple{9.9});
+    stack.AddParticle(std::tuple{8.8});
+    const double sumS = 9.9 + 8.8; // helper, see below
+    CHECK(stack.getSize() == 2);
+    CHECK(stack.getEntries() == 2);
+    CHECK(!stack.IsEmpty());
 
-    auto particle = s.GetNextParticle();
+    auto particle = stack.GetNextParticle();
 
     StackTestView view(particle);
-    REQUIRE(view.GetSize() == 0);
+    CHECK(view.getSize() == 0);
+    CHECK(view.getEntries() == 0);
+    CHECK(view.IsEmpty());
 
     {
       auto proj = view.GetProjectile();
-      REQUIRE(proj.GetData() == particle.GetData());
+      CHECK(proj.GetData() == particle.GetData());
       proj.AddSecondary(std::tuple{4.4});
     }
+    CHECK(view.getSize() == 1);
+    CHECK(view.getEntries() == 1);
+    CHECK(!view.IsEmpty());
+    CHECK(stack.getSize() == 3);
+    CHECK(stack.getEntries() == 3);
+    CHECK(!stack.IsEmpty());
 
     view.AddSecondary(std::tuple{4.5});
     view.AddSecondary(std::tuple{4.6});
+    CHECK(view.getSize() == 3);
+    CHECK(view.getEntries() == 3);
+    CHECK(!view.IsEmpty());
+    CHECK(stack.getSize() == 5);
+    CHECK(stack.getEntries() == 5);
+    CHECK(!stack.IsEmpty());
 
-    REQUIRE(view.GetSize() == 3);
-    REQUIRE(s.GetSize() == 5);
-    REQUIRE(!view.IsEmpty());
-
-    auto sumView = [](const StackTestView& stack) {
-      double value = 0;
-      for (const auto& p : stack) { value += p.GetData(); }
-      return value;
-    };
+    CHECK(sum(stack) == sumS + 4.4 + 4.5 + 4.6);
+    CHECK(sumView(view) == 4.4 + 4.5 + 4.6);
 
-    REQUIRE(sum(s) == sumS + 4.4 + 4.5 + 4.6);
-    REQUIRE(sumView(view) == 4.4 + 4.5 + 4.6);
+    view.last().Delete();
+    CHECK(view.getSize() == 3);
+    CHECK(view.getEntries() == 2);
+    CHECK(stack.getSize() == 5);
+    CHECK(stack.getEntries() == 4);
 
-    view.DeleteLast();
-    REQUIRE(view.GetSize() == 2);
-    REQUIRE(s.GetSize() == 4);
-
-    REQUIRE(sum(s) == sumS + 4.4 + 4.5);
-    REQUIRE(sumView(view) == 4.4 + 4.5);
+    CHECK(sum(stack) == sumS + 4.4 + 4.5);
+    CHECK(sumView(view) == 4.4 + 4.5);
 
     auto pDel = view.GetNextParticle();
     view.Delete(pDel);
-    REQUIRE(view.GetSize() == 1);
-    REQUIRE(s.GetSize() == 3);
+    CHECK(view.getSize() == 2);
+    CHECK(stack.getSize() == 4);
 
-    REQUIRE(sum(s) == sumS + 4.4 + 4.5 - pDel.GetData());
-    REQUIRE(sumView(view) == 4.4 + 4.5 - pDel.GetData());
+    CHECK(sum(stack) == sumS + 4.4 + 4.5 - pDel.GetData());
+    CHECK(sumView(view) == 4.4 + 4.5 - pDel.GetData());
 
     view.Delete(view.GetNextParticle());
-    REQUIRE(sum(s) == sumS);
-    REQUIRE(sumView(view) == 0);
-    REQUIRE(view.IsEmpty());
+    CHECK(sum(stack) == sumS);
+    CHECK(sumView(view) == 0);
+    CHECK(view.IsEmpty());
 
     {
       auto proj = view.GetProjectile();
-      REQUIRE(proj.GetData() == particle.GetData());
+      CHECK(proj.GetData() == particle.GetData());
+      CHECK(particle == view.parent());
     }
+
+    // at() and first()
+    auto pTestAt = view.at(0);
+    auto pTestFirst = view.first();
+    CHECK(pTestFirst == pTestAt);
   }
 
   SECTION("secondary view, construct from ParticleType") {
-    StackTest s;
-    REQUIRE(s.GetSize() == 0);
-    s.AddParticle(std::tuple{9.9});
-    s.AddParticle(std::tuple{8.8});
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    stack.AddParticle(std::tuple{9.9});
+    stack.AddParticle(std::tuple{8.8});
 
-    auto iterator = s.GetNextParticle();
+    auto iterator = stack.GetNextParticle();
     typename StackTest::ParticleType& particle = iterator; // as in corsika::Cascade
 
     StackTestView view(particle);
-    REQUIRE(view.GetSize() == 0);
+    CHECK(view.getSize() == 0);
 
     view.AddSecondary(std::tuple{4.4});
 
-    REQUIRE(view.GetSize() == 1);
+    CHECK(view.getSize() == 1);
   }
 
   SECTION("deletion") {
@@ -141,21 +167,20 @@ TEST_CASE("SecondaryStack", "[stack]") {
       proj.AddSecondary(std::tuple{1.});
       proj.AddSecondary(std::tuple{2.});
 
-      CHECK(stack.GetSize() == 6); // -99, 0, -2, -1, 1, 2
-      CHECK(view.GetSize() == 4);  // -2, -1, 1, 2
+      CHECK(stack.getSize() == 6); // -99, 0, -2, -1, 1, 2
+      CHECK(view.getSize() == 4);  // -2, -1, 1, 2
 
       // now delete all negative entries, i.e. -1 and -2
       auto p = view.begin();
       while (p != view.end()) {
         auto data = p.GetData();
-        if (data < 0) {
-          p.Delete();
-        } else {
-          ++p;
-        }
+        if (data < 0) { p.Delete(); }
+        ++p;
       }
-      CHECK(stack.GetSize() == 4); // -99, 0, 2, 1 (order changes during deletion)
-      CHECK(view.GetSize() == 2);  // 2, 1
+      CHECK(stack.getSize() == 6);
+      CHECK(stack.getEntries() == 4); // -99, 0, 2, 1 (order changes during deletion)
+      CHECK(view.getSize() == 4);
+      CHECK(view.getEntries() == 2); // 2, 1
     }
 
     // repeat
@@ -175,17 +200,57 @@ TEST_CASE("SecondaryStack", "[stack]") {
       auto p = view.begin();
       while (p != view.end()) {
         auto data = p.GetData();
-        if (data < 0) {
-          p.Delete();
-        } else {
-          ++p;
-        }
+        if (data < 0) { p.Delete(); }
+        ++p;
       }
 
       // stack should contain -99, 0, 2, 1, [2, 1]
       // view should contain 1, 2
 
-      CHECK(stack.GetSize() == 6);
+      CHECK(stack.getEntries() == 6);
+      CHECK(stack.getSize() == 10);
     }
   }
+
+  SECTION("swap particle") {
+    StackTest stack;
+    stack.AddParticle(std::tuple{-99.});
+
+    StackTestView view(stack.first());
+    view.AddSecondary(std::tuple{-2.});
+    view.AddSecondary(std::tuple{-1.});
+    view.AddSecondary(std::tuple{1.});
+
+    auto p1 = view.begin();
+    auto p2 = p1 + 1;
+
+    CHECK(p1.GetData() == -2.);
+    CHECK(p2.GetData() == -1.);
+
+    view.Swap(p1, p2);
+
+    CHECK(p1.GetData() == -1);
+    CHECK(p2.GetData() == -2);
+  }
+
+  SECTION("copy particle") {
+    StackTest stack;
+    stack.AddParticle(std::tuple{-99.});
+
+    StackTestView view(stack.first());
+    view.AddSecondary(std::tuple{-2.});
+    view.AddSecondary(std::tuple{-1.});
+    view.AddSecondary(std::tuple{1.});
+
+    auto p1 = view.begin();
+    auto p2 = p1 + 1;
+
+    CHECK(p1.GetData() == -2.);
+    CHECK(p2.GetData() == -1.);
+
+    view.Copy(p1, p2);
+
+    CHECK(p1.GetData() == -2);
+    CHECK(p2.GetData() == -2);
+  }
 }
diff --git a/Framework/StackInterface/testStackInterface.cc b/Framework/StackInterface/testStackInterface.cc
index 1ef504f60255ec5a45d540560f6e9fc19822bc29..af1f217ad361f8b4fa41c62da12b156a205e1c76 100644
--- a/Framework/StackInterface/testStackInterface.cc
+++ b/Framework/StackInterface/testStackInterface.cc
@@ -6,6 +6,8 @@
  * the license.
  */
 
+#define protected public // to also test the internal state of objects
+
 #include <corsika/stack/Stack.h>
 
 #include <testTestStack.h> // simple test-stack for testing. This is
@@ -13,7 +15,6 @@
                            // CMAKE_CURRENT_SOURCE_DIR
 
 #include <iomanip>
-#include <iostream>
 #include <tuple>
 #include <vector>
 
@@ -37,12 +38,20 @@ TEST_CASE("Stack", "[Stack]") {
   SECTION("StackInterface") {
 
     // construct a valid Stack object
-    StackTest s;
-    s.Clear();
-    s.AddParticle(std::tuple{0.});
-    s.Copy(s.cbegin(), s.begin());
-    s.Swap(s.begin(), s.begin());
-    REQUIRE(s.GetSize() == 1);
+    StackTest stack;
+    stack.Clear();
+    CHECK(stack.getSize() == 0);
+    CHECK(stack.IsEmpty());                          // stack empty here
+    auto pTest0 = stack.AddParticle(std::tuple{0.}); // [0]
+    CHECK(stack.getSize() == 1);
+    CHECK(!stack.IsEmpty());
+    auto pTest1 = stack.AddParticle(std::tuple{1.}); // [0,1]
+    CHECK(stack.getSize() == 2);
+    CHECK(pTest1.GetData() == 1.);
+    auto pTestAt = stack.at(1); // -> 1
+    CHECK(pTestAt == pTest1);
+    auto pTestFirst = stack.first(); // -> 0
+    CHECK(pTestFirst == pTest0);
   }
 
   SECTION("construct") {
@@ -53,65 +62,154 @@ TEST_CASE("Stack", "[Stack]") {
 
   SECTION("write and read") {
 
-    StackTest s;
-    s.AddParticle(std::tuple{9.9});
-    const double v = sum(s);
-    REQUIRE(v == 9.9);
+    StackTest stack;
+    stack.AddParticle(std::tuple{9.9});
+    const double v = sum(stack);
+    CHECK(v == 9.9);
   }
 
   SECTION("delete from stack") {
 
-    StackTest s;
-    REQUIRE(s.GetSize() == 0);
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
     StackTest::StackIterator p =
-        s.AddParticle(std::tuple{0.}); // valid way to access particle data
+        stack.AddParticle(std::tuple{0.}); // valid way to access particle data
     p.SetData(9.9);
-    REQUIRE(s.GetSize() == 1);
-    s.Delete(p);
-    REQUIRE(s.GetSize() == 0);
+    CHECK(stack.getSize() == 1);
+    CHECK(stack.getEntries() == 1);
+    stack.Delete(p);
+    CHECK(stack.getSize() == 1);
+    CHECK(stack.getEntries() == 0);
   }
 
   SECTION("delete particle") {
 
-    StackTest s;
-    REQUIRE(s.GetSize() == 0);
-    auto p = s.AddParticle(
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    stack.AddParticle(std::tuple{8.9});
+    stack.AddParticle(std::tuple{7.9});
+    auto p = stack.AddParticle(
         std::tuple{9.9}); // also valid way to access particle data, identical to above
-    REQUIRE(s.GetSize() == 1);
-    p.Delete();
-    REQUIRE(s.GetSize() == 0);
+
+    CHECK(stack.getSize() == 3);
+    CHECK(stack.getEntries() == 3);
+    CHECK(!stack.IsEmpty());
+
+    p.Delete(); // mark for deletion: size=3, entries=2
+    CHECK(stack.getSize() == 3);
+    CHECK(stack.getEntries() == 2);
+    CHECK(!stack.IsEmpty());
+
+    stack.last().Delete(); // mark for deletion: size=3, entries=1
+    CHECK(stack.getSize() == 3);
+    CHECK(stack.getEntries() == 1);
+    CHECK(!stack.IsEmpty());
+
+    /*
+       GetNextParticle will find two entries marked as "deleted" and
+       will purge this from the end of the stack: size = 1
+    */
+    stack.GetNextParticle().Delete(); // mark for deletion: size=3, entries=0
+    CHECK(stack.getSize() == 1);
+    CHECK(stack.getEntries() == 0);
+    CHECK(stack.IsEmpty());
   }
 
   SECTION("create secondaries") {
 
-    StackTest s;
-    REQUIRE(s.GetSize() == 0);
-    auto iter = s.AddParticle(std::tuple{9.9});
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    auto iter = stack.AddParticle(std::tuple{9.9});
     StackTest::ParticleInterfaceType& p =
         *iter; // also this is valid to access particle data
-    REQUIRE(s.GetSize() == 1);
+    CHECK(stack.getSize() == 1);
     p.AddSecondary(std::tuple{4.4});
-    REQUIRE(s.GetSize() == 2);
-    /*p.AddSecondary(3.3, 2.2);
-    REQUIRE(s.GetSize() == 3);
-    double v = 0;
-    for (auto& p : s) { v += p.GetData(); }
-    REQUIRE(v == 9.9 + 4.4 + 3.3 + 2.2);*/
+    CHECK(stack.getSize() == 2);
   }
 
   SECTION("get next particle") {
-    StackTest s;
-    REQUIRE(s.GetSize() == 0);
-    s.AddParticle(std::tuple{9.9});
-    s.AddParticle(std::tuple{8.8});
-    auto particle = s.GetNextParticle(); // first particle
-    REQUIRE(particle.GetData() == 8.8);
-
-    particle.Delete();
-    auto particle2 = s.GetNextParticle(); // first particle
-    REQUIRE(particle2.GetData() == 9.9);
-    particle2.Delete();
-
-    REQUIRE(s.GetSize() == 0);
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    CHECK(stack.getEntries() == 0);
+    CHECK(stack.IsEmpty());
+
+    stack.AddParticle(std::tuple{9.9});
+    stack.AddParticle(std::tuple{8.8});
+    CHECK(stack.getSize() == 2);
+    CHECK(stack.getEntries() == 2);
+    CHECK(!stack.IsEmpty());
+
+    auto particle = stack.GetNextParticle(); // first particle
+    CHECK(particle.GetData() == 8.8);
+
+    particle.Delete(); // only marks (last) particle as deleted
+    CHECK(stack.getSize() == 2);
+    CHECK(stack.getEntries() == 1);
+    CHECK(!stack.IsEmpty());
+
+    /*
+      This following call to GetNextParticle will realize that the
+      current last particle on the stack was marked "deleted" and will
+      purge it: stack size is reduced by one.
+     */
+    auto particle2 = stack.GetNextParticle(); // first particle
+    CHECK(particle2.GetData() == 9.9);
+    CHECK(stack.getSize() == 1);
+    CHECK(stack.getEntries() == 1);
+    CHECK(!stack.IsEmpty());
+
+    particle2.Delete(); // also mark this particle as deleted
+
+    CHECK(stack.getSize() == 1);
+    CHECK(stack.getEntries() == 0);
+    CHECK(stack.IsEmpty());
+  }
+
+  SECTION("swap particle") {
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    CHECK(stack.getEntries() == 0);
+    CHECK(stack.IsEmpty());
+
+    stack.AddParticle(std::tuple{9.888});
+    stack.AddParticle(std::tuple{8.999});
+    CHECK(stack.getSize() == 2);
+    CHECK(stack.getEntries() == 2);
+    CHECK(!stack.IsEmpty());
+
+    auto p1 = stack.begin();
+    auto p2 = p1 + 1;
+
+    CHECK(p1.GetData() == 9.888);
+    CHECK(p2.GetData() == 8.999);
+
+    stack.Swap(p1, p2);
+
+    CHECK(p1.GetData() == 8.999);
+    CHECK(p2.GetData() == 9.888);
+  }
+
+  SECTION("copy particle") {
+    StackTest stack;
+    CHECK(stack.getSize() == 0);
+    CHECK(stack.getEntries() == 0);
+    CHECK(stack.IsEmpty());
+
+    stack.AddParticle(std::tuple{9.888});
+    stack.AddParticle(std::tuple{8.999});
+    CHECK(stack.getSize() == 2);
+    CHECK(stack.getEntries() == 2);
+    CHECK(!stack.IsEmpty());
+
+    auto p1 = stack.begin();
+    auto p2 = p1 + 1;
+
+    CHECK(p1.GetData() == 9.888);
+    CHECK(p2.GetData() == 8.999);
+
+    stack.Copy(p1, p2);
+
+    CHECK(p1.GetData() == 9.888);
+    CHECK(p2.GetData() == 9.888);
   }
 }
diff --git a/Framework/StackInterface/testTestStack.h b/Framework/StackInterface/testTestStack.h
index 88e5fa1c6c9e06d1e55481f5f44849a92d6d8bc6..9765dda8b5cf449844ff0e44a6b922007d8f0cb2 100644
--- a/Framework/StackInterface/testTestStack.h
+++ b/Framework/StackInterface/testTestStack.h
@@ -78,13 +78,6 @@ public:
                        std::tuple<double> v) {
     SetData(std::get<0>(v));
   }
-  /// alternative set-particle data for non-standard construction from different inputs
-  /*
-  void SetParticleData(const double v, const double p) { SetData(v + p); }
-  void SetParticleData(TestParticleInterface<StackIteratorInterface>&,
-                       const double v, const double p) {
-    SetData(v + p);
-    }*/
 
   // here are the fundamental methods for access to TestStackData data
   void SetData(const double v) { GetStackData().SetData(GetIndex(), v); }
diff --git a/Framework/Units/PhysicalUnits.h b/Framework/Units/PhysicalUnits.h
index 06ca5374fbfb5a8e334c1634b0c697f75819d4cd..1f207abbc2f3c08bc7b39c49ca3a5db0719e6e5d 100644
--- a/Framework/Units/PhysicalUnits.h
+++ b/Framework/Units/PhysicalUnits.h
@@ -30,6 +30,19 @@ namespace phys::units {
  *
  */
 
+namespace corsika::units {
+  template <int N, typename T>
+  auto constexpr static_pow([[maybe_unused]] T x) {
+    if constexpr (N == 0) {
+      return 1;
+    } else if constexpr (N > 0) {
+      return x * static_pow<N - 1, T>(x);
+    } else {
+      return 1 / static_pow<-N, T>(x);
+    }
+  }
+} // namespace corsika::units
+
 namespace corsika::units::si {
   using namespace phys::units;
   using namespace phys::units::literals;
@@ -67,19 +80,6 @@ namespace corsika::units::si {
   using InverseGrammageType =
       phys::units::quantity<phys::units::dimensions<2, -1, 0>, double>;
 
-  namespace detail {
-    template <int N, typename T>
-    auto constexpr static_pow([[maybe_unused]] T x) {
-      if constexpr (N == 0) {
-        return 1;
-      } else if constexpr (N > 0) {
-        return x * static_pow<N - 1, T>(x);
-      } else {
-        return 1 / static_pow<-N, T>(x);
-      }
-    }
-  } // namespace detail
-
   template <typename DimFrom, typename DimTo>
   auto constexpr ConversionFactorHEPToSI() {
     static_assert(DimFrom::dim1 == 0 && DimFrom::dim2 == 0 && DimFrom::dim3 == 0 &&
diff --git a/Framework/Units/testUnits.cc b/Framework/Units/testUnits.cc
index 9f033d243f66c41deb044a912d08dbc3d2d5d003..bae1ce1d7123d82c9878bc4a4bbd319f33ce9912 100644
--- a/Framework/Units/testUnits.cc
+++ b/Framework/Units/testUnits.cc
@@ -114,7 +114,7 @@ TEST_CASE("PhysicalUnits", "[Units]") {
   }
 
   SECTION("static_pow") {
-    using namespace corsika::units::si::detail;
+    using namespace corsika::units;
     double x = 235.7913;
     REQUIRE(1 == static_pow<0, double>(x));
     REQUIRE(x == static_pow<1, double>(x));
diff --git a/Processes/CONEXSourceCut/CONEXSourceCut.cc b/Processes/CONEXSourceCut/CONEXSourceCut.cc
index 77fffb0e65363aada42d45b4f5ab1c6ba7aa9a47..8039d8ed867053756f631a950815aaf50d9c380f 100644
--- a/Processes/CONEXSourceCut/CONEXSourceCut.cc
+++ b/Processes/CONEXSourceCut/CONEXSourceCut.cc
@@ -29,17 +29,17 @@ corsika::process::EProcessReturn CONEXSourceCut::DoSecondaries(
 
     auto const it = std::find_if(egs_em_codes_.cbegin(), egs_em_codes_.cend(),
                                  [=](auto const& p) { return pid == p.first; });
-    if (it == egs_em_codes_.cend()) {
-      ++p;
-      continue; // no EM particle
-    }
+    if (it != egs_em_codes_.cend()) {
+      // EM particle
 
-    auto const egs_pid = it->second;
+      auto const egs_pid = it->second;
 
-    addParticle(egs_pid, p.GetEnergy(), p.GetMass(), p.GetPosition(),
-                p.GetMomentum().normalized(), p.GetTime());
+      addParticle(egs_pid, p.GetEnergy(), p.GetMass(), p.GetPosition(),
+                  p.GetMomentum().normalized(), p.GetTime());
 
-    p.Delete();
+      p.Delete();
+    }
+    ++p;
   }
 
   return corsika::process::EProcessReturn::eOk;
diff --git a/Processes/CONEXSourceCut/testCONEXSourceCut.cc b/Processes/CONEXSourceCut/testCONEXSourceCut.cc
index 18df797fe78d62d216df5c5e91de0b1dc0b4c400..82d208521e4b2fd86aca6349305c5dbaec609e58 100644
--- a/Processes/CONEXSourceCut/testCONEXSourceCut.cc
+++ b/Processes/CONEXSourceCut/testCONEXSourceCut.cc
@@ -55,10 +55,9 @@ TEST_CASE("CONEXSourceCut") {
 
   auto const observationHeight = 1.4_km + conex::earthRadius;
   auto const injectionHeight = 112.75_km + conex::earthRadius;
-  auto const t =
-      -observationHeight * cos(thetaRad) +
-      sqrt(-units::si::detail::static_pow<2>(sin(thetaRad) * observationHeight) +
-           units::si::detail::static_pow<2>(injectionHeight));
+  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 +
diff --git a/Processes/HadronicElasticModel/HadronicElasticModel.cc b/Processes/HadronicElasticModel/HadronicElasticModel.cc
index d95c69d657cb36709ad25ae6febb3e293709e9dc..cdc01d9a92aa741de6b3af9775d021063c34d644 100644
--- a/Processes/HadronicElasticModel/HadronicElasticModel.cc
+++ b/Processes/HadronicElasticModel/HadronicElasticModel.cc
@@ -6,11 +6,10 @@
  * the license.
  */
 
-#include <corsika/process/hadronic_elastic_model/HadronicElasticModel.h>
-
 #include <corsika/environment/Environment.h>
 #include <corsika/environment/NuclearComposition.h>
 #include <corsika/geometry/FourVector.h>
+#include <corsika/process/hadronic_elastic_model/HadronicElasticModel.h>
 #include <corsika/random/ExponentialDistribution.h>
 #include <corsika/utl/COMBoost.h>
 
@@ -19,178 +18,177 @@
 #include <iomanip>
 #include <iostream>
 
-using namespace corsika::setup;
 using SetupParticle = corsika::setup::Stack::ParticleType;
+using SetupView = corsika::setup::StackView;
 
-namespace corsika::process::HadronicElasticModel {
-
-  HadronicElasticInteraction::HadronicElasticInteraction(units::si::CrossSectionType x,
-                                                         units::si::CrossSectionType y)
-      : fX(x)
-      , fY(y) {}
-
-  template <>
-  units::si::GrammageType HadronicElasticInteraction::GetInteractionLength(
-      SetupParticle const& p) {
-    using namespace units::si;
-    if (p.GetPID() == particles::Code::Proton) {
-      auto const* currentNode = p.GetNode();
-      auto const& mediumComposition =
-          currentNode->GetModelProperties().GetNuclearComposition();
-
-      auto const& components = mediumComposition.GetComponents();
-      auto const& fractions = mediumComposition.GetFractions();
-
-      auto const projectileMomentum = p.GetMomentum();
-      auto const projectileMomentumSquaredNorm = projectileMomentum.squaredNorm();
-      auto const projectileEnergy = p.GetEnergy();
-
-      auto const avgCrossSection = [&]() {
-        CrossSectionType avgCrossSection = 0_b;
-
-        for (size_t i = 0; i < fractions.size(); ++i) {
-          auto const targetMass = particles::GetMass(components[i]);
-          auto const s = detail::static_pow<2>(projectileEnergy + targetMass) -
-                         projectileMomentumSquaredNorm;
-          avgCrossSection += CrossSection(s) * fractions[i];
-        }
-
-        std::cout << "avgCrossSection: " << avgCrossSection / 1_mb << " mb" << std::endl;
-
-        return avgCrossSection;
-      }();
-
-      auto const avgTargetMassNumber = mediumComposition.GetAverageMassNumber();
+using namespace corsika::process::HadronicElasticModel;
+using namespace corsika;
 
-      GrammageType const interactionLength =
-          avgTargetMassNumber * units::constants::u / avgCrossSection;
+HadronicElasticInteraction::HadronicElasticInteraction(units::si::CrossSectionType x,
+                                                       units::si::CrossSectionType y)
+    : fX(x)
+    , fY(y) {}
 
-      return interactionLength;
-    } else {
-      return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm);
-    }
-  }
-
-  template <>
-  process::EProcessReturn HadronicElasticInteraction::DoInteraction(SetupParticle& p) {
-    if (p.GetPID() != particles::Code::Proton) { return process::EProcessReturn::eOk; }
-
-    using namespace units::si;
-    using namespace units::constants;
+template <>
+units::si::GrammageType HadronicElasticInteraction::GetInteractionLength(
+    SetupParticle const& p) {
+  using namespace units::si;
+  if (p.GetPID() == particles::Code::Proton) {
+    auto const* currentNode = p.GetNode();
+    auto const& mediumComposition =
+        currentNode->GetModelProperties().GetNuclearComposition();
 
-    const auto* currentNode = p.GetNode();
-    const auto& composition = currentNode->GetModelProperties().GetNuclearComposition();
-    const auto& components = composition.GetComponents();
-
-    std::vector<units::si::CrossSectionType> cross_section_of_components(
-        composition.GetComponents().size());
+    auto const& components = mediumComposition.GetComponents();
+    auto const& fractions = mediumComposition.GetFractions();
 
     auto const projectileMomentum = p.GetMomentum();
     auto const projectileMomentumSquaredNorm = projectileMomentum.squaredNorm();
     auto const projectileEnergy = p.GetEnergy();
 
-    for (size_t i = 0; i < components.size(); ++i) {
-      auto const targetMass = particles::GetMass(components[i]);
-      auto const s = units::si::detail::static_pow<2>(projectileEnergy + targetMass) -
-                     projectileMomentumSquaredNorm;
-      cross_section_of_components[i] = CrossSection(s);
-    }
-
-    const auto targetCode = composition.SampleTarget(cross_section_of_components, fRNG);
-
-    auto const targetMass = particles::GetMass(targetCode);
+    auto const avgCrossSection = [&]() {
+      CrossSectionType avgCrossSection = 0_b;
 
-    std::uniform_real_distribution phiDist(0., 2 * M_PI);
+      for (size_t i = 0; i < fractions.size(); ++i) {
+        auto const targetMass = particles::GetMass(components[i]);
+        auto const s = units::static_pow<2>(projectileEnergy + targetMass) -
+                       projectileMomentumSquaredNorm;
+        avgCrossSection += CrossSection(s) * fractions[i];
+      }
 
-    geometry::FourVector const projectileLab(projectileEnergy, projectileMomentum);
-    geometry::FourVector const targetLab(
-        targetMass, geometry::Vector<units::si::hepmomentum_d>(
-                        projectileMomentum.GetCoordinateSystem(), {0_eV, 0_eV, 0_eV}));
-    utl::COMBoost const boost(projectileLab, targetMass);
+      std::cout << "avgCrossSection: " << avgCrossSection / 1_mb << " mb" << std::endl;
 
-    auto const projectileCoM = boost.toCoM(projectileLab);
-    auto const targetCoM = boost.toCoM(targetLab);
-
-    auto const pProjectileCoMSqNorm =
-        projectileCoM.GetSpaceLikeComponents().squaredNorm();
-    auto const pProjectileCoMNorm = sqrt(pProjectileCoMSqNorm);
+      return avgCrossSection;
+    }();
 
-    auto const eProjectileCoM = projectileCoM.GetTimeLikeComponent();
-    auto const eTargetCoM = targetCoM.GetTimeLikeComponent();
+    auto const avgTargetMassNumber = mediumComposition.GetAverageMassNumber();
 
-    auto const sqrtS = eProjectileCoM + eTargetCoM;
-    auto const s = units::si::detail::static_pow<2>(sqrtS);
+    GrammageType const interactionLength =
+        avgTargetMassNumber * units::constants::u / avgCrossSection;
 
-    auto const B = this->B(s);
-    std::cout << B << std::endl;
+    return interactionLength;
+  } else {
+    return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm);
+  }
+}
 
-    random::ExponentialDistribution tDist(1 / B);
-    auto const absT = [&]() {
-      decltype(tDist(fRNG)) absT;
-      auto const maxT = 4 * pProjectileCoMSqNorm;
+template <>
+process::EProcessReturn HadronicElasticInteraction::DoInteraction(SetupView& view) {
+  using namespace units::si;
+  using namespace units::constants;
 
-      do {
-        // |t| cannot become arbitrarily large, max. given by GER eq. (4.16), so we just
-        // throw again until we have an acceptable value. Note that the formula holds in
-        // any frame despite of what is stated in the book.
-        absT = tDist(fRNG);
-      } while (absT >= maxT);
+  auto p = view.GetProjectile();
+  if (p.GetPID() != particles::Code::Proton) { return process::EProcessReturn::eOk; }
 
-      return absT;
-    }();
+  const auto* currentNode = p.GetNode();
+  const auto& composition = currentNode->GetModelProperties().GetNuclearComposition();
+  const auto& components = composition.GetComponents();
 
-    std::cout << "HadronicElasticInteraction: s = " << s * invGeVsq
-              << " GeV²; absT = " << absT * invGeVsq
-              << " GeV² (max./GeV² = " << 4 * invGeVsq * projectileMomentumSquaredNorm
-              << ')' << std::endl;
-
-    auto const theta = 2 * asin(sqrt(absT / (4 * pProjectileCoMSqNorm)));
-    auto const phi = phiDist(fRNG);
-
-    auto const projectileScatteredLab = boost.fromCoM(
-        geometry::FourVector<HEPEnergyType, geometry::Vector<hepmomentum_d>>(
-            eProjectileCoM,
-            geometry::Vector<hepmomentum_d>(projectileMomentum.GetCoordinateSystem(),
-                                            {pProjectileCoMNorm * sin(theta) * cos(phi),
-                                             pProjectileCoMNorm * sin(theta) * sin(phi),
-                                             pProjectileCoMNorm * cos(theta)})));
-
-    p.SetMomentum(projectileScatteredLab.GetSpaceLikeComponents());
-    p.SetEnergy(
-        sqrt(projectileScatteredLab.GetSpaceLikeComponents().squaredNorm() +
-             units::si::detail::static_pow<2>(particles::GetMass(
-                 p.GetPID())))); // Don't use energy from boost. It can be smaller than
-                                 // the momentum due to limited numerical accuracy.
-
-    return process::EProcessReturn::eOk;
-  }
+  std::vector<units::si::CrossSectionType> cross_section_of_components(
+      composition.GetComponents().size());
 
-  HadronicElasticInteraction::inveV2 HadronicElasticInteraction::B(eV2 s) const {
-    using namespace units::constants;
-    auto constexpr b_p = 2.3;
-    auto const result =
-        (2 * b_p + 2 * b_p + 4 * pow(s * invGeVsq, gfEpsilon) - 4.2) * invGeVsq;
-    std::cout << "B(" << s << ") = " << result / invGeVsq << " GeV¯²" << std::endl;
-    return result;
-  }
+  auto const projectileMomentum = p.GetMomentum();
+  auto const projectileMomentumSquaredNorm = projectileMomentum.squaredNorm();
+  auto const projectileEnergy = p.GetEnergy();
 
-  units::si::CrossSectionType HadronicElasticInteraction::CrossSection(
-      SquaredHEPEnergyType s) const {
-    using namespace units::si;
-    using namespace units::constants;
-    // assuming every target behaves like a proton, fX and fY are universal
-    CrossSectionType const sigmaTotal =
-        fX * pow(s * invGeVsq, gfEpsilon) + fY * pow(s * invGeVsq, -gfEta);
-
-    // according to Schuler & Sjöstrand, PRD 49, 2257 (1994)
-    // (we ignore rho because rho^2 is just ~2 %)
-    auto const sigmaElastic =
-        units::si::detail::static_pow<2>(sigmaTotal) /
-        (16 * M_PI * ConvertHEPToSI<CrossSectionType::dimension_type>(B(s)));
-
-    std::cout << "HEM sigmaTot = " << sigmaTotal / 1_mb << " mb" << std::endl;
-    std::cout << "HEM sigmaElastic = " << sigmaElastic / 1_mb << " mb" << std::endl;
-    return sigmaElastic;
+  for (size_t i = 0; i < components.size(); ++i) {
+    auto const targetMass = particles::GetMass(components[i]);
+    auto const s = units::static_pow<2>(projectileEnergy + targetMass) -
+                   projectileMomentumSquaredNorm;
+    cross_section_of_components[i] = CrossSection(s);
   }
 
-} // namespace corsika::process::HadronicElasticModel
+  const auto targetCode = composition.SampleTarget(cross_section_of_components, fRNG);
+
+  auto const targetMass = particles::GetMass(targetCode);
+
+  std::uniform_real_distribution phiDist(0., 2 * M_PI);
+
+  geometry::FourVector const projectileLab(projectileEnergy, projectileMomentum);
+  geometry::FourVector const targetLab(
+      targetMass, geometry::Vector<units::si::hepmomentum_d>(
+                      projectileMomentum.GetCoordinateSystem(), {0_eV, 0_eV, 0_eV}));
+  utl::COMBoost const boost(projectileLab, targetMass);
+
+  auto const projectileCoM = boost.toCoM(projectileLab);
+  auto const targetCoM = boost.toCoM(targetLab);
+
+  auto const pProjectileCoMSqNorm = projectileCoM.GetSpaceLikeComponents().squaredNorm();
+  auto const pProjectileCoMNorm = sqrt(pProjectileCoMSqNorm);
+
+  auto const eProjectileCoM = projectileCoM.GetTimeLikeComponent();
+  auto const eTargetCoM = targetCoM.GetTimeLikeComponent();
+
+  auto const sqrtS = eProjectileCoM + eTargetCoM;
+  auto const s = units::static_pow<2>(sqrtS);
+
+  auto const B = this->B(s);
+  std::cout << B << std::endl;
+
+  random::ExponentialDistribution tDist(1 / B);
+  auto const absT = [&]() {
+    decltype(tDist(fRNG)) absT;
+    auto const maxT = 4 * pProjectileCoMSqNorm;
+
+    do {
+      // |t| cannot become arbitrarily large, max. given by GER eq. (4.16), so we just
+      // throw again until we have an acceptable value.
+      absT = tDist(fRNG);
+    } while (absT >= maxT);
+
+    return absT;
+  }();
+
+  std::cout << "HadronicElasticInteraction: s = " << s * invGeVsq
+            << " GeV²; absT = " << absT * invGeVsq
+            << " GeV² (max./GeV² = " << 4 * invGeVsq * projectileMomentumSquaredNorm
+            << ')' << std::endl;
+
+  auto const theta = 2 * asin(sqrt(absT / (4 * pProjectileCoMSqNorm)));
+  auto const phi = phiDist(fRNG);
+
+  auto const projectileScatteredLab =
+      boost.fromCoM(geometry::FourVector<HEPEnergyType, geometry::Vector<hepmomentum_d>>(
+          eProjectileCoM,
+          geometry::Vector<hepmomentum_d>(projectileMomentum.GetCoordinateSystem(),
+                                          {pProjectileCoMNorm * sin(theta) * cos(phi),
+                                           pProjectileCoMNorm * sin(theta) * sin(phi),
+                                           pProjectileCoMNorm * cos(theta)})));
+
+  view.AddSecondary(
+      std::tuple<particles::Code, units::si::HEPEnergyType,
+                 corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
+          p.GetPID(),
+          sqrt(projectileScatteredLab.GetSpaceLikeComponents().squaredNorm() +
+               units::static_pow<2>(particles::GetMass(p.GetPID()))),
+          projectileScatteredLab.GetSpaceLikeComponents(), p.GetPosition(), p.GetTime()});
+
+  return process::EProcessReturn::eOk;
+}
+
+HadronicElasticInteraction::inveV2 HadronicElasticInteraction::B(eV2 s) const {
+  using namespace units::constants;
+  auto constexpr b_p = 2.3;
+  auto const result =
+      (2 * b_p + 2 * b_p + 4 * pow(s * invGeVsq, gfEpsilon) - 4.2) * invGeVsq;
+  std::cout << "B(" << s << ") = " << result / invGeVsq << " GeV¯²" << std::endl;
+  return result;
+}
+
+units::si::CrossSectionType HadronicElasticInteraction::CrossSection(
+    SquaredHEPEnergyType s) const {
+  using namespace units::si;
+  using namespace units::constants;
+  // assuming every target behaves like a proton, fX and fY are universal
+  CrossSectionType const sigmaTotal =
+      fX * pow(s * invGeVsq, gfEpsilon) + fY * pow(s * invGeVsq, -gfEta);
+
+  // according to Schuler & Sjöstrand, PRD 49, 2257 (1994)
+  // (we ignore rho because rho^2 is just ~2 %)
+  auto const sigmaElastic =
+      units::static_pow<2>(sigmaTotal) /
+      (16 * M_PI * ConvertHEPToSI<CrossSectionType::dimension_type>(B(s)));
+
+  std::cout << "HEM sigmaTot = " << sigmaTotal / 1_mb << " mb" << std::endl;
+  std::cout << "HEM sigmaElastic = " << sigmaElastic / 1_mb << " mb" << std::endl;
+  return sigmaElastic;
+}
diff --git a/Processes/HadronicElasticModel/HadronicElasticModel.h b/Processes/HadronicElasticModel/HadronicElasticModel.h
index b719a30819c38e0057821e0af19208c118cbff4e..3273bfec15aad2a47128a169e1031790f0dda3c6 100644
--- a/Processes/HadronicElasticModel/HadronicElasticModel.h
+++ b/Processes/HadronicElasticModel/HadronicElasticModel.h
@@ -54,8 +54,8 @@ namespace corsika::process::HadronicElasticModel {
     template <typename Particle>
     corsika::units::si::GrammageType GetInteractionLength(Particle const& p);
 
-    template <typename Particle>
-    corsika::process::EProcessReturn DoInteraction(Particle&);
+    template <typename TStackView>
+    corsika::process::EProcessReturn DoInteraction(TStackView&);
   };
 
 } // namespace corsika::process::HadronicElasticModel
diff --git a/Processes/InteractionCounter/CMakeLists.txt b/Processes/InteractionCounter/CMakeLists.txt
index f828ac98a064f97abb357390a11772abeb8d6ecb..7602d4e7c8250d8960d1686290846a5e826d4283 100644
--- a/Processes/InteractionCounter/CMakeLists.txt
+++ b/Processes/InteractionCounter/CMakeLists.txt
@@ -1,7 +1,7 @@
 set (
   MODEL_HEADERS
-  InteractionCounter.h
-  InteractionHistogram.h
+  InteractionCounter.hpp
+  InteractionHistogram.hpp
   )
 
 set (
diff --git a/Processes/InteractionCounter/InteractionCounter.h b/Processes/InteractionCounter/InteractionCounter.hpp
similarity index 91%
rename from Processes/InteractionCounter/InteractionCounter.h
rename to Processes/InteractionCounter/InteractionCounter.hpp
index 2ca7db507620b2143991e0d6830225a0bcb8373b..83bc130011b1f77e8101143af4cdb2684e46b18b 100644
--- a/Processes/InteractionCounter/InteractionCounter.h
+++ b/Processes/InteractionCounter/InteractionCounter.hpp
@@ -8,9 +8,10 @@
 
 #pragma once
 
+#include <corsika/process/interaction_counter/InteractionHistogram.hpp>
+
 #include <corsika/process/InteractionProcess.h>
 #include <corsika/process/ProcessSequence.h>
-#include <corsika/process/interaction_counter/InteractionHistogram.h>
 #include <corsika/setup/SetupStack.h>
 
 namespace corsika::process::interaction_counter {
@@ -30,8 +31,9 @@ namespace corsika::process::interaction_counter {
     InteractionCounter(TCountedProcess& process)
         : process_(process) {}
 
-    template <typename TProjectile>
-    auto DoInteraction(TProjectile& projectile) {
+    template <typename TSecondaryView>
+    auto DoInteraction(TSecondaryView& view) {
+      auto const projectile = view.GetProjectile();
       auto const massNumber = projectile.GetNode()
                                   ->GetModelProperties()
                                   .GetNuclearComposition()
@@ -46,7 +48,7 @@ namespace corsika::process::interaction_counter {
       } else {
         histogram_.fill(projectile_id, projectile.GetEnergy(), massTarget);
       }
-      return process_.DoInteraction(projectile);
+      return process_.DoInteraction(view);
     }
 
     template <typename TParticle>
diff --git a/Processes/InteractionCounter/InteractionHistogram.cc b/Processes/InteractionCounter/InteractionHistogram.cc
index 583184b0765fe470b3a2c53b2c5855ade0fa9fec..a54b63bd185cbc0d2892bc564f1c294889d5e9eb 100644
--- a/Processes/InteractionCounter/InteractionHistogram.cc
+++ b/Processes/InteractionCounter/InteractionHistogram.cc
@@ -6,7 +6,7 @@
  * the license.
  */
 
-#include <corsika/process/interaction_counter/InteractionHistogram.h>
+#include <corsika/process/interaction_counter/InteractionHistogram.hpp>
 
 #include <fstream>
 #include <string>
diff --git a/Processes/InteractionCounter/InteractionHistogram.h b/Processes/InteractionCounter/InteractionHistogram.hpp
similarity index 99%
rename from Processes/InteractionCounter/InteractionHistogram.h
rename to Processes/InteractionCounter/InteractionHistogram.hpp
index 9d060a4ef85f73a2165567f15d53359572543ff6..ec9124e491d2be28eab999561fde2c7c831e5224 100644
--- a/Processes/InteractionCounter/InteractionHistogram.h
+++ b/Processes/InteractionCounter/InteractionHistogram.hpp
@@ -6,6 +6,8 @@
  * the license.
  */
 
+#pragma once
+
 #include <corsika/particles/ParticleProperties.h>
 #include <corsika/units/PhysicalUnits.h>
 
diff --git a/Processes/InteractionCounter/testInteractionCounter.cc b/Processes/InteractionCounter/testInteractionCounter.cc
index 8b47f1bf67b1a411315d5ac6303cfb5c365583fb..08fc95113c2955b5782247d55133336d15e01d5b 100644
--- a/Processes/InteractionCounter/testInteractionCounter.cc
+++ b/Processes/InteractionCounter/testInteractionCounter.cc
@@ -6,7 +6,7 @@
  * the license.
  */
 
-#include <corsika/process/interaction_counter/InteractionCounter.h>
+#include <corsika/process/interaction_counter/InteractionCounter.hpp>
 
 #include <corsika/environment/Environment.h>
 #include <corsika/environment/HomogeneousMedium.h>
@@ -59,9 +59,8 @@ auto setupStack(int vA, int vZ, HEPEnergyType vMomentum, TNodeType* vNodePtr,
   geometry::Point const origin(cs, {0_m, 0_m, 0_m});
   corsika::stack::MomentumVector const pLab(cs, {vMomentum, 0_GeV, 0_GeV});
 
-  HEPEnergyType const E0 =
-      sqrt(units::si::detail::static_pow<2>(mN * vA) + pLab.squaredNorm());
-  auto particle =
+  HEPEnergyType const E0 = sqrt(units::static_pow<2>(mN * vA) + pLab.squaredNorm());
+  setup::Stack::StackIterator particle =
       stack->AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
                                     corsika::stack::MomentumVector, geometry::Point,
                                     units::si::TimeType, unsigned short, unsigned short>{
@@ -69,8 +68,7 @@ auto setupStack(int vA, int vZ, HEPEnergyType vMomentum, TNodeType* vNodePtr,
 
   particle.SetNode(vNodePtr);
   return std::make_tuple(
-      std::move(stack),
-      std::make_unique<decltype(corsika::stack::SecondaryView(particle))>(particle));
+      std::move(stack), std::make_unique<decltype(setup::StackView(particle))>(particle));
 }
 
 template <typename TNodeType>
@@ -81,9 +79,8 @@ auto setupStack(particles::Code vProjectileType, HEPEnergyType vMomentum,
   geometry::Point const origin(cs, {0_m, 0_m, 0_m});
   corsika::stack::MomentumVector const pLab(cs, {vMomentum, 0_GeV, 0_GeV});
 
-  HEPEnergyType const E0 =
-      sqrt(units::si::detail::static_pow<2>(particles::GetMass(vProjectileType)) +
-           pLab.squaredNorm());
+  HEPEnergyType const E0 = sqrt(
+      units::static_pow<2>(particles::GetMass(vProjectileType)) + pLab.squaredNorm());
   auto particle = stack->AddParticle(
       std::tuple<particles::Code, units::si::HEPEnergyType,
                  corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
@@ -91,8 +88,7 @@ auto setupStack(particles::Code vProjectileType, HEPEnergyType vMomentum,
 
   particle.SetNode(vNodePtr);
   return std::make_tuple(
-      std::move(stack),
-      std::make_unique<decltype(corsika::stack::SecondaryView(particle))>(particle));
+      std::move(stack), std::make_unique<decltype(setup::StackView(particle))>(particle));
 }
 
 struct DummyProcess {
@@ -108,6 +104,9 @@ struct DummyProcess {
 };
 
 TEST_CASE("InteractionCounter") {
+
+  logging::SetLevel(logging::level::debug);
+
   DummyProcess d;
   InteractionCounter countedProcess(d);
 
@@ -121,11 +120,10 @@ TEST_CASE("InteractionCounter") {
   SECTION("DoInteraction nucleus") {
     unsigned short constexpr A = 14, Z = 7;
     auto [stackPtr, secViewPtr] = setupStack(A, Z, 105_TeV, nodePtr, *csPtr);
-    REQUIRE(stackPtr->GetSize() == 1);
-    REQUIRE(secViewPtr->GetSize() == 0);
+    REQUIRE(stackPtr->getEntries() == 1);
+    REQUIRE(secViewPtr->getEntries() == 0);
 
-    auto projectile = secViewPtr->GetProjectile();
-    auto const ret = countedProcess.DoInteraction(projectile);
+    auto const ret = countedProcess.DoInteraction(*secViewPtr);
     REQUIRE(ret == nullptr);
 
     auto const& h = countedProcess.GetHistogram().labHists().second.at(1'000'070'140);
@@ -141,11 +139,10 @@ TEST_CASE("InteractionCounter") {
     auto constexpr code = particles::Code::Lambda0;
     auto constexpr codeInt = static_cast<particles::CodeIntType>(code);
     auto [stackPtr, secViewPtr] = setupStack(code, 105_TeV, nodePtr, *csPtr);
-    REQUIRE(stackPtr->GetSize() == 1);
-    REQUIRE(secViewPtr->GetSize() == 0);
+    REQUIRE(stackPtr->getEntries() == 1);
+    REQUIRE(secViewPtr->getEntries() == 0);
 
-    auto projectile = secViewPtr->GetProjectile();
-    auto const ret = countedProcess.DoInteraction(projectile);
+    auto const ret = countedProcess.DoInteraction(*secViewPtr);
     REQUIRE(ret == nullptr);
 
     auto const& h = countedProcess.GetHistogram().labHists().first;
diff --git a/Processes/ObservationPlane/CMakeLists.txt b/Processes/ObservationPlane/CMakeLists.txt
index e1c3c41799d3d3d17a6680de995aba3a64f35933..800c5dbd6b2e900f8e2228fe9e64615f304c4d28 100644
--- a/Processes/ObservationPlane/CMakeLists.txt
+++ b/Processes/ObservationPlane/CMakeLists.txt
@@ -21,6 +21,7 @@ target_link_libraries (
   ProcessObservationPlane
   CORSIKAgeometry
   CORSIKAprocesssequence
+  CORSIKAlogging
   )
 
 target_include_directories (
diff --git a/Processes/OnShellCheck/testOnShellCheck.cc b/Processes/OnShellCheck/testOnShellCheck.cc
index cfc7d159b5a7884a56dd45e19ee8c8c42f35901d..0d45b1e2946d814d47039d11f74249ace3282526 100644
--- a/Processes/OnShellCheck/testOnShellCheck.cc
+++ b/Processes/OnShellCheck/testOnShellCheck.cc
@@ -37,11 +37,10 @@ TEST_CASE("OnShellCheck", "[processes]") {
   // two energies
   const HEPEnergyType E = 10_GeV;
   // list of arbitrary particles
-  std::array<particles::Code, 4> particleList = {
-      particles::Code::PiPlus, particles::Code::PiMinus, particles::Code::Helium,
-      particles::Code::Gamma};
+  std::array const particleList{particles::Code::PiPlus, particles::Code::PiMinus,
+                                particles::Code::Helium, particles::Code::Gamma};
 
-  std::array<double, 4> mass_shifts = {1.1, 1.001, 1.0, 1.0};
+  std::array const mass_shifts{1.1, 1.001, 1.0, 1.0};
 
   SECTION("check particle masses") {
 
@@ -57,7 +56,7 @@ TEST_CASE("OnShellCheck", "[processes]") {
             corsika::stack::MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
             geometry::Point(rootCS, 0_m, 0_m, 0_m), 0_ns});
     // view on secondary particles
-    corsika::stack::SecondaryView view(particle);
+    setup::StackView view{particle};
     // ref. to primary particle through the secondary view.
     // only this way the secondary view is populated
     auto projectile = view.GetProjectile();
diff --git a/Processes/ParticleCut/ParticleCut.cc b/Processes/ParticleCut/ParticleCut.cc
index 1616f6248740d7b8e6f23a8c31405f57fff16eb8..72a97ee065d654799c7ae72cb4742a0d8d0a1452 100644
--- a/Processes/ParticleCut/ParticleCut.cc
+++ b/Processes/ParticleCut/ParticleCut.cc
@@ -90,11 +90,8 @@ namespace corsika::process {
     EProcessReturn ParticleCut::DoSecondaries(corsika::setup::StackView& vS) {
       auto particle = vS.begin();
       while (particle != vS.end()) {
-        if (checkCutParticle(particle)) {
-          particle.Delete();
-        } else {
-          ++particle; // next entry in SecondaryView
-        }
+        if (checkCutParticle(particle)) { particle.Delete(); }
+        ++particle; // next entry in SecondaryView
       }
       return EProcessReturn::eOk;
     }
diff --git a/Processes/ParticleCut/ParticleCut.h b/Processes/ParticleCut/ParticleCut.h
index 57315bf4ec36c73a9aecbde0245d65cbde702dfb..4705051d4261028c49caca1af39066563fd1bff6 100644
--- a/Processes/ParticleCut/ParticleCut.h
+++ b/Processes/ParticleCut/ParticleCut.h
@@ -42,7 +42,6 @@ namespace corsika::process {
         return units::si::meter * std::numeric_limits<double>::infinity();
       }
 
-
       units::si::HEPEnergyType GetECut() const { return fECut; }
       units::si::HEPEnergyType GetInvEnergy() const { return fInvEnergy; }
       units::si::HEPEnergyType GetCutEnergy() const { return fEnergy; }
@@ -62,8 +61,6 @@ namespace corsika::process {
 
       bool ParticleIsEmParticle(particles::Code) const;
       bool ParticleIsInvisible(particles::Code) const;
-
-
     };
   } // namespace particle_cut
 } // namespace corsika::process
diff --git a/Processes/ParticleCut/testParticleCut.cc b/Processes/ParticleCut/testParticleCut.cc
index e7647a55d7a1b3884d6672adc42f02757577be38..2092636fa372628080769d22c52679289d9d045b 100644
--- a/Processes/ParticleCut/testParticleCut.cc
+++ b/Processes/ParticleCut/testParticleCut.cc
@@ -55,7 +55,7 @@ TEST_CASE("ParticleCut", "[processes]") {
             corsika::stack::MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
             geometry::Point(rootCS, 0_m, 0_m, 0_m), 0_ns});
     // view on secondary particles
-    corsika::stack::SecondaryView view(particle);
+    setup::StackView view(particle);
     // ref. to primary particle through the secondary view.
     // only this way the secondary view is populated
     auto projectile = view.GetProjectile();
@@ -70,7 +70,7 @@ TEST_CASE("ParticleCut", "[processes]") {
 
     cut.DoSecondaries(view);
 
-    CHECK(view.GetSize() == 9);
+    CHECK(view.getEntries() == 9);
   }
 
   SECTION("cut on particle type: em") {
@@ -85,7 +85,7 @@ TEST_CASE("ParticleCut", "[processes]") {
             corsika::stack::MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
             geometry::Point(rootCS, 0_m, 0_m, 0_m), 0_ns});
     // view on secondary particles
-    corsika::stack::SecondaryView view(particle);
+    corsika::setup::StackView view(particle);
     // ref. to primary particle through the secondary view.
     // only this way the secondary view is populated
     auto projectile = view.GetProjectile();
@@ -100,7 +100,7 @@ TEST_CASE("ParticleCut", "[processes]") {
 
     cut.DoSecondaries(view);
 
-    CHECK(view.GetSize() == 9);
+    CHECK(view.getEntries() == 9);
   }
 
   SECTION("cut low energy") {
@@ -114,7 +114,7 @@ TEST_CASE("ParticleCut", "[processes]") {
             corsika::stack::MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
             geometry::Point(rootCS, 0_m, 0_m, 0_m), 0_ns});
     // view on secondary particles
-    corsika::stack::SecondaryView view(particle);
+    setup::StackView view{particle};
     // ref. to primary particle through the secondary view.
     // only this way the secondary view is populated
     auto projectile = view.GetProjectile();
@@ -129,6 +129,7 @@ TEST_CASE("ParticleCut", "[processes]") {
 
     cut.DoSecondaries(view);
 
-    CHECK(view.GetSize() == 0);
+    REQUIRE(view.getEntries() == 0);
+    REQUIRE(view.getSize() == 10);
   }
 }
diff --git a/Processes/Proposal/Interaction.cc b/Processes/Proposal/Interaction.cc
index 064e146ea3cd245f03219f5ca51f880e20f41184..b2417b4260baec292ed779dbc912de9b1a5bd11e 100644
--- a/Processes/Proposal/Interaction.cc
+++ b/Processes/Proposal/Interaction.cc
@@ -49,48 +49,51 @@ namespace corsika::process::proposal {
   }
 
   template <>
-  corsika::process::EProcessReturn Interaction::DoInteraction(
-      setup::StackView::StackIterator& vP) {
+  corsika::process::EProcessReturn Interaction::DoInteraction(setup::StackView& view) {
     using namespace corsika::units::si; // required for operator::_MeV
 
-    if (CanInteract(vP.GetPID())) {
+    auto const projectile = view.GetProjectile();
+
+    if (CanInteract(projectile.GetPID())) {
       // Get or build corresponding calculators
-      auto c = GetCalculator(vP, calc);
+      auto c = GetCalculator(projectile, calc);
 
       // Get the rates of the interaction types for every component.
       std::uniform_real_distribution<double> distr(0., 1.);
 
       // sample a interaction-type, loss and component
-      auto rates = get<eINTERACTION>(c->second)->Rates(vP.GetEnergy() / 1_MeV);
+      auto rates = get<eINTERACTION>(c->second)->Rates(projectile.GetEnergy() / 1_MeV);
       auto [type, comp_ptr, v] = get<eINTERACTION>(c->second)->SampleLoss(
-          vP.GetEnergy() / 1_MeV, rates, distr(fRNG));
+          projectile.GetEnergy() / 1_MeV, rates, distr(fRNG));
 
       // Read how much random numbers are required to calculate the secondaries.
       // Calculate the secondaries and deploy them on the corsika stack.
       auto rnd =
           vector<double>(get<eSECONDARIES>(c->second)->RequiredRandomNumbers(type));
       for (auto& it : rnd) it = distr(fRNG);
-      auto point = PROPOSAL::Vector3D(vP.GetPosition().GetX() / 1_cm,
-                                      vP.GetPosition().GetY() / 1_cm,
-                                      vP.GetPosition().GetZ() / 1_cm);
-      auto vP_dir = vP.GetDirection();
-      auto d = vP_dir.GetComponents();
+      auto point = PROPOSAL::Vector3D(projectile.GetPosition().GetX() / 1_cm,
+                                      projectile.GetPosition().GetY() / 1_cm,
+                                      projectile.GetPosition().GetZ() / 1_cm);
+      auto projectile_dir = projectile.GetDirection();
+      auto d = projectile_dir.GetComponents();
       auto direction = PROPOSAL::Vector3D(d.GetX().magnitude(), d.GetY().magnitude(),
                                           d.GetZ().magnitude());
       auto loss = make_tuple(static_cast<int>(type), point, direction,
-                             v * vP.GetEnergy() / 1_MeV, 0.);
+                             v * projectile.GetEnergy() / 1_MeV, 0.);
       auto sec = get<eSECONDARIES>(c->second)->CalculateSecondaries(
-          vP.GetEnergy() / 1_MeV, loss, *comp_ptr, rnd);
+          projectile.GetEnergy() / 1_MeV, loss, *comp_ptr, rnd);
       for (auto& s : sec) {
         auto E = get<PROPOSAL::Loss::ENERGY>(s) * 1_MeV;
         auto vec = corsika::geometry::QuantityVector(
             get<PROPOSAL::Loss::DIRECTION>(s).GetX() * E,
             get<PROPOSAL::Loss::DIRECTION>(s).GetY() * E,
             get<PROPOSAL::Loss::DIRECTION>(s).GetZ() * E);
-        auto p = corsika::stack::MomentumVector(vP_dir.GetCoordinateSystem(), vec);
+        auto p =
+            corsika::stack::MomentumVector(projectile_dir.GetCoordinateSystem(), vec);
         auto sec_code = corsika::particles::ConvertFromPDG(
             static_cast<particles::PDGCode>(get<PROPOSAL::Loss::TYPE>(s)));
-        vP.AddSecondary(make_tuple(sec_code, E, p, vP.GetPosition(), vP.GetTime()));
+        view.AddSecondary(
+            make_tuple(sec_code, E, p, projectile.GetPosition(), projectile.GetTime()));
       }
     }
     return process::EProcessReturn::eOk;
@@ -98,13 +101,13 @@ namespace corsika::process::proposal {
 
   template <>
   corsika::units::si::GrammageType Interaction::GetInteractionLength(
-      setup::Stack::StackIterator const& vP) {
+      setup::Stack::StackIterator const& projectile) {
     using namespace corsika::units::si; // required for operator::_MeV
 
-    if (CanInteract(vP.GetPID())) {
-      auto c = GetCalculator(vP, calc);
-      return get<eINTERACTION>(c->second)->MeanFreePath(vP.GetEnergy() / 1_MeV) * 1_g /
-             (1_cm * 1_cm);
+    if (CanInteract(projectile.GetPID())) {
+      auto c = GetCalculator(projectile, calc);
+      return get<eINTERACTION>(c->second)->MeanFreePath(projectile.GetEnergy() / 1_MeV) *
+             1_g / (1_cm * 1_cm);
     }
     return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm);
   }
diff --git a/Processes/Proposal/Interaction.h b/Processes/Proposal/Interaction.h
index b506dc73973ec3077efc7e751e801293aa41c443..7617e8c27d229068ed95e7afefc975f5b957941b 100644
--- a/Processes/Proposal/Interaction.h
+++ b/Processes/Proposal/Interaction.h
@@ -52,8 +52,8 @@ namespace corsika::process::proposal {
     //! pair of interaction-type, component and rate, followed by sampling a loss and
     //! produce the corresponding secondaries and store them on the particle stack.
     //!
-    template <typename Particle>
-    corsika::process::EProcessReturn DoInteraction(Particle&);
+    template <typename TSecondaryView>
+    corsika::process::EProcessReturn DoInteraction(TSecondaryView&);
 
     //!
     //! Calculates the  mean free path length
diff --git a/Processes/Pythia/Decay.cc b/Processes/Pythia/Decay.cc
index ea81121009d25363335ce8fb0261d5327fa39f37..4caf02487afa2d7a89d600a33a2c46eefb00c294 100644
--- a/Processes/Pythia/Decay.cc
+++ b/Processes/Pythia/Decay.cc
@@ -22,7 +22,7 @@ using std::vector;
 
 using namespace corsika;
 using namespace corsika::setup;
-using Projectile = corsika::setup::StackView::ParticleType;
+using View = corsika::setup::StackView;
 using Particle = corsika::setup::Stack::ParticleType;
 using Track = Trajectory;
 
@@ -184,21 +184,23 @@ namespace corsika::process::pythia {
   }
 
   template <>
-  void Decay::DoDecay(Projectile& vP) {
+  void Decay::DoDecay(View& view) {
     using geometry::Point;
     using namespace units;
     using namespace units::si;
 
-    auto const& decayPoint = vP.GetPosition();
-    auto const t0 = vP.GetTime();
+    auto const projectile = view.GetProjectile();
 
-    auto const& labMomentum = vP.GetMomentum();
+    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, vP.GetMass());
+    utl::COMBoost const boost(labMomentum, projectile.GetMass());
     auto const& rotatedCS = boost.GetRotatedCS();
 
     fCount++;
@@ -207,7 +209,7 @@ namespace corsika::process::pythia {
     Pythia8::Event& event = fPythia.event;
     event.reset();
 
-    auto const particleId = vP.GetPID();
+    auto const particleId = projectile.GetPID();
 
     // set particle unstable
     Decay::SetUnstable(particleId);
@@ -218,7 +220,7 @@ namespace corsika::process::pythia {
     double constexpr px = 0;
     double constexpr py = 0;
     double constexpr pz = 0;
-    double const en = vP.GetMass() / 1_GeV;
+    double const en = projectile.GetMass() / 1_GeV;
     double const m = en;
 
     // add particle to pythia stack
@@ -248,7 +250,7 @@ namespace corsika::process::pythia {
              << fourMomLab.GetSpaceLikeComponents().GetComponents(labCS) / 1_GeV
              << " energy=" << fourMomLab.GetTimeLikeComponent() << endl;
 
-        vP.AddSecondary(
+        view.AddSecondary(
             tuple<particles::Code, units::si::HEPEnergyType,
                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
                 pyId, fourMomLab.GetTimeLikeComponent(),
diff --git a/Processes/Pythia/Decay.h b/Processes/Pythia/Decay.h
index dfa1a25808579cb969dfd756d4a1f704a79c12e0..fb4708df15e3c7948a0d352d2ebcd326af68aa53 100644
--- a/Processes/Pythia/Decay.h
+++ b/Processes/Pythia/Decay.h
@@ -60,8 +60,8 @@ namespace corsika::process {
        In this function PYTHIA is called to execute the decay of the input particle.
      */
 
-      template <typename TProjectile>
-      void DoDecay(TProjectile&);
+      template <typename TSecondaryView>
+      void DoDecay(TSecondaryView&);
 
     private:
       void SetUnstable(const corsika::particles::Code);
diff --git a/Processes/Pythia/Interaction.cc b/Processes/Pythia/Interaction.cc
index f6b2a01403ce6bc3462c6d476fb94f1299c6bbf3..fe641a62629a5bfc2b75f4ce9d0ada6c984322ce 100644
--- a/Processes/Pythia/Interaction.cc
+++ b/Processes/Pythia/Interaction.cc
@@ -22,7 +22,7 @@ using std::tuple;
 
 using namespace corsika;
 using namespace corsika::setup;
-using Projectile = corsika::setup::StackView::ParticleType;
+using SecondaryView = corsika::setup::StackView;
 using Particle = corsika::setup::Stack::ParticleType;
 
 namespace corsika::process::pythia {
@@ -241,14 +241,16 @@ namespace corsika::process::pythia {
    */
 
   template <>
-  process::EProcessReturn Interaction::DoInteraction(Projectile& vP) {
+  process::EProcessReturn Interaction::DoInteraction(SecondaryView& view) {
 
     using namespace units;
     using namespace utl;
     using namespace units::si;
     using namespace geometry;
 
-    const auto corsikaBeamId = vP.GetPID();
+    auto const projectile = view.GetProjectile();
+
+    const auto corsikaBeamId = projectile.GetPID();
     cout << "Pythia::Interaction: "
          << "DoInteraction: " << corsikaBeamId << " interaction? "
          << process::pythia::Interaction::CanInteract(corsikaBeamId) << endl;
@@ -264,8 +266,8 @@ namespace corsika::process::pythia {
           RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
 
       // position and time of interaction, not used in Sibyll
-      Point pOrig = vP.GetPosition();
-      TimeType tOrig = vP.GetTime();
+      Point pOrig = projectile.GetPosition();
+      TimeType tOrig = projectile.GetTime();
 
       // define target
       // FOR NOW: target is always at rest
@@ -274,8 +276,8 @@ namespace corsika::process::pythia {
       const FourVector PtargLab(eTargetLab, pTargetLab);
 
       // define projectile
-      HEPEnergyType const eProjectileLab = vP.GetEnergy();
-      auto const pProjectileLab = vP.GetMomentum();
+      HEPEnergyType const eProjectileLab = projectile.GetEnergy();
+      auto const pProjectileLab = projectile.GetMomentum();
 
       cout << "Interaction: ebeam lab: " << eProjectileLab / 1_GeV << endl
            << "Interaction: pbeam lab: " << pProjectileLab.GetComponents() / 1_GeV
@@ -310,12 +312,12 @@ namespace corsika::process::pythia {
       cout << "Interaction: time: " << tOrig << endl;
 
       HEPEnergyType Etot = eProjectileLab + eTargetLab;
-      MomentumVector Ptot = vP.GetMomentum();
+      MomentumVector Ptot = projectile.GetMomentum();
       // invariant mass, i.e. cm. energy
       HEPEnergyType Ecm = sqrt(Etot * Etot - Ptot.squaredNorm());
 
       // sample target mass number
-      const auto* currentNode = vP.GetNode();
+      const auto* currentNode = projectile.GetNode();
       const auto& mediumComposition =
           currentNode->GetModelProperties().GetNuclearComposition();
       // get cross sections for target materials
@@ -381,7 +383,7 @@ namespace corsika::process::pythia {
           HEPEnergyType const pyEn = p8p.e() * 1_GeV;
 
           // add to corsika stack
-          auto pnew = vP.AddSecondary(
+          auto pnew = view.AddSecondary(
               tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
                     geometry::Point, units::si::TimeType>{pyId, pyEn, pyPlab, pOrig,
                                                           tOrig});
diff --git a/Processes/Pythia/Interaction.h b/Processes/Pythia/Interaction.h
index d6e3862bf72c3b1728c75931ee7bffb509adeed2..4b06aeb6d58f6bf034b49b054376e540fa279628 100644
--- a/Processes/Pythia/Interaction.h
+++ b/Processes/Pythia/Interaction.h
@@ -54,8 +54,8 @@ namespace corsika::process::pythia {
        event is copied (and boosted) into the shower lab frame.
      */
 
-    template <typename TProjectile>
-    corsika::process::EProcessReturn DoInteraction(TProjectile&);
+    template <typename TSecondaryView>
+    corsika::process::EProcessReturn DoInteraction(TSecondaryView&);
 
   private:
     corsika::random::RNG& fRNG =
diff --git a/Processes/Pythia/testPythia8.cc b/Processes/Pythia/testPythia8.cc
index f947a8b813fbe20add8fbc869f79313fb527b7f1..6a8ae279badbbb7336357fefb208f926d55b4748 100644
--- a/Processes/Pythia/testPythia8.cc
+++ b/Processes/Pythia/testPythia8.cc
@@ -130,14 +130,13 @@ TEST_CASE("pythia process") {
 
     random::RNGManager::GetInstance().RegisterRandomStream("pythia");
 
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
+    setup::StackView view(particle);
 
     process::pythia::Decay model;
 
     [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
-    model.DoDecay(projectile);
-    CHECK(stack.GetSize() == 3);
+    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));
@@ -180,12 +179,11 @@ TEST_CASE("pythia process") {
                    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();
+    setup::StackView view(particle);
 
     process::pythia::Interaction model;
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
   }
 }
diff --git a/Processes/QGSJetII/CMakeLists.txt b/Processes/QGSJetII/CMakeLists.txt
index 8e3bb52d415abf20b352c8bbd3268d9dd5e5e841..77d43794c751b59713b6d962a4cc26177a0b81aa 100644
--- a/Processes/QGSJetII/CMakeLists.txt
+++ b/Processes/QGSJetII/CMakeLists.txt
@@ -104,20 +104,4 @@ target_link_libraries (
   testQGSJetII
   ProcessQGSJetII
   CORSIKAtesting
-  )
-
-# also provide QGSJetII large data tables for testing in build tree (links)
-# -> changed that to use environment variable "${CORSIKA_DATA}/QGSJetII"
-# add_custom_command (
-#  OUTPUT  ${CMAKE_CURRENT_BINARY_DIR}/qgsdat-II-04 ${CMAKE_CURRENT_BINARY_DIR}/sectnu-II-04
-#  COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/qgsdat-II-04 ${CMAKE_CURRENT_BINARY_DIR}/qgsdat-II-04
-#  COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/sectnu-II-04 ${CMAKE_CURRENT_BINARY_DIR}/sectnu-II-04
-#  COMMENT "Generate link in source-dir: qgsjet-II-04"
-#  )
-
-# add_custom_target (QGSJetDataLink DEPENDS
-#  ${CMAKE_CURRENT_BINARY_DIR}/qgsdat-II-04
-#  ${CMAKE_CURRENT_BINARY_DIR}/sectnu-II-04
-#  )
-
-# add_dependencies (testQGSJetII QGSJetDataLink)
+  stdc++fs)
diff --git a/Processes/QGSJetII/Interaction.cc b/Processes/QGSJetII/Interaction.cc
index c7a2ffc1846e96fdeb4df54bd044ddad33ce834a..c16233af582c5908261f88e6de89af58748b0e09 100644
--- a/Processes/QGSJetII/Interaction.cc
+++ b/Processes/QGSJetII/Interaction.cc
@@ -33,7 +33,7 @@ using std::tuple;
 using namespace corsika;
 using namespace corsika::setup;
 using SetupParticle = setup::Stack::StackIterator;
-using SetupProjectile = setup::StackView::StackIterator;
+using SetupView = setup::StackView;
 using Track = Trajectory;
 
 namespace corsika::process::qgsjetII {
@@ -169,14 +169,16 @@ namespace corsika::process::qgsjetII {
    */
 
   template <>
-  process::EProcessReturn Interaction::DoInteraction(SetupProjectile& vP) {
+  process::EProcessReturn Interaction::DoInteraction(SetupView& view) {
 
     using namespace units;
     using namespace utl;
     using namespace units::si;
     using namespace geometry;
 
-    const auto corsikaBeamId = vP.GetPID();
+    auto const projectile = view.GetProjectile();
+
+    const auto corsikaBeamId = projectile.GetPID();
     cout << "ProcessQgsjetII: "
          << "DoInteraction: " << corsikaBeamId << " interaction? "
          << process::qgsjetII::CanInteract(corsikaBeamId) << endl;
@@ -187,8 +189,8 @@ namespace corsika::process::qgsjetII {
           RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
 
       // position and time of interaction, not used in QgsjetII
-      Point pOrig = vP.GetPosition();
-      TimeType tOrig = vP.GetTime();
+      Point pOrig = projectile.GetPosition();
+      TimeType tOrig = projectile.GetTime();
 
       // define target
       // for QgsjetII is always a single nucleon
@@ -198,11 +200,11 @@ namespace corsika::process::qgsjetII {
       const FourVector PtargLab(targetEnergyLab, targetMomentumLab);
 
       // define projectile
-      HEPEnergyType const projectileEnergyLab = vP.GetEnergy();
-      auto const projectileMomentumLab = vP.GetMomentum();
+      HEPEnergyType const projectileEnergyLab = projectile.GetEnergy();
+      auto const projectileMomentumLab = projectile.GetMomentum();
 
       int beamA = 1;
-      if (particles::IsNucleus(corsikaBeamId)) beamA = vP.GetNuclearA();
+      if (particles::IsNucleus(corsikaBeamId)) beamA = projectile.GetNuclearA();
 
       const HEPEnergyType projectileEnergyLabPerNucleon = projectileEnergyLab / beamA;
 
@@ -217,7 +219,7 @@ namespace corsika::process::qgsjetII {
       cout << "Interaction: time: " << tOrig << endl;
 
       // sample target mass number
-      auto const* currentNode = vP.GetNode();
+      auto const* currentNode = projectile.GetNode();
       auto const& mediumComposition =
           currentNode->GetModelProperties().GetNuclearComposition();
       // get cross sections for target materials
@@ -257,7 +259,7 @@ namespace corsika::process::qgsjetII {
       QgsjetIIHadronType qgsjet_hadron_type =
           process::qgsjetII::GetQgsjetIIHadronType(corsikaBeamId);
       if (qgsjet_hadron_type == QgsjetIIHadronType::NucleusType) {
-        projectileMassNumber = vP.GetNuclearA();
+        projectileMassNumber = projectile.GetNuclearA();
         if (projectileMassNumber > maxMassNumber_)
           throw std::runtime_error("QgsjetII projectile mass outside range.");
         std::array<QgsjetIIHadronType, 2> constexpr nucleons = {
@@ -325,7 +327,7 @@ namespace corsika::process::qgsjetII {
             momentum.rebase(originalCS); // transform back into standard lab frame
             std::cout << "secondary fragment> id=" << idFragm
                       << " p=" << momentum.GetComponents() << std::endl;
-            auto pnew = vP.AddSecondary(
+            auto pnew = view.AddSecondary(
                 tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
                       geometry::Point, units::si::TimeType>{idFragm, energy, momentum,
                                                             pOrig, tOrig});
@@ -361,7 +363,7 @@ namespace corsika::process::qgsjetII {
           std::cout << "secondary fragment> id=" << idFragm
                     << " p=" << momentum.GetComponents() << " A=" << A << " Z=" << Z
                     << std::endl;
-          auto pnew = vP.AddSecondary(
+          auto pnew = view.AddSecondary(
               tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
                     geometry::Point, units::si::TimeType, unsigned short, unsigned short>{
                   idFragm, energy, momentum, pOrig, tOrig, A, Z});
@@ -381,7 +383,7 @@ namespace corsika::process::qgsjetII {
         std::cout << "secondary fragment> id="
                   << process::qgsjetII::ConvertFromQgsjetII(psec.GetPID())
                   << " p=" << momentum.GetComponents() << std::endl;
-        auto pnew = vP.AddSecondary(
+        auto pnew = view.AddSecondary(
             tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
                   geometry::Point, units::si::TimeType>{
                 process::qgsjetII::ConvertFromQgsjetII(psec.GetPID()), energy, momentum,
@@ -396,7 +398,7 @@ namespace corsika::process::qgsjetII {
            << QGSJetIIFragmentsStackData::GetWoundedNucleonsTarget()
            << ", N_wounded,proj="
            << QGSJetIIFragmentsStackData::GetWoundedNucleonsProjectile()
-           << ", N_fragm,proj=" << qfs.GetSize() << endl;
+           << ", N_fragm,proj=" << qfs.getEntries() << endl;
     }
     return process::EProcessReturn::eOk;
   }
diff --git a/Processes/QGSJetII/Interaction.h b/Processes/QGSJetII/Interaction.h
index fc44b8f884e487ce1ed904c47d7af3be2b51a177..bef66e3ce3ac61ea8975aaf89ed4ae3645b4d811 100644
--- a/Processes/QGSJetII/Interaction.h
+++ b/Processes/QGSJetII/Interaction.h
@@ -50,8 +50,8 @@ namespace corsika::process::qgsjetII {
        event is copied (and boosted) into the shower lab frame.
      */
 
-    template <typename TProjectile>
-    corsika::process::EProcessReturn DoInteraction(TProjectile&);
+    template <typename TSecondaryView>
+    corsika::process::EProcessReturn DoInteraction(TSecondaryView&);
 
   private:
     corsika::random::RNG& rng_ =
diff --git a/Processes/QGSJetII/testQGSJetII.cc b/Processes/QGSJetII/testQGSJetII.cc
index 5acb717a51b77ef05d50b7eaf6b04e9bee674f2b..eafe725dbcd676afcf402ea562a589d228f468dc 100644
--- a/Processes/QGSJetII/testQGSJetII.cc
+++ b/Processes/QGSJetII/testQGSJetII.cc
@@ -18,6 +18,10 @@
 
 #include <catch2/catch.hpp>
 
+#include <cstdlib>
+#include <experimental/filesystem>
+#include <iostream>
+
 using namespace corsika;
 using namespace corsika::process::qgsjetII;
 using namespace corsika::units::si;
@@ -40,8 +44,30 @@ auto sumMomentum(TStackView const& view, geometry::CoordinateSystem const& vCS)
   return sum;
 }
 
+TEST_CASE("CORSIKA_DATA", "[processes]") {
+
+  SECTION("check CORSIKA_DATA") {
+
+    const char* data = std::getenv("CORSIKA_DATA");
+    // these REQUIRES are needed:
+    REQUIRE(data != 0);
+    REQUIRE(std::experimental::filesystem::is_directory(
+        std::experimental::filesystem::path(std::string(data) + "/QGSJetII")));
+    std::cout << "data: " << data << " isDir: "
+              << std::experimental::filesystem::is_directory(std::string(data) +
+                                                             "/QGSJetII")
+              << std::endl;
+  }
+}
+
 TEST_CASE("QgsjetII", "[processes]") {
 
+  SECTION("Corsika -> QgsjetII") {
+    CHECK(process::qgsjetII::ConvertToQgsjetII(particles::PiMinus::GetCode()) ==
+          process::qgsjetII::QgsjetIICode::PiMinus);
+    CHECK(process::qgsjetII::ConvertToQgsjetIIRaw(particles::Proton::GetCode()) == 2);
+  }
+
   SECTION("QgsjetII -> Corsika") {
     CHECK(particles::PiPlus::GetCode() == process::qgsjetII::ConvertFromQgsjetII(
                                               process::qgsjetII::QgsjetIICode::PiPlus));
@@ -133,13 +159,13 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
             particles::Code::Proton, E0, plab, pos, 0_ns});
 
     particle.SetNode(nodePtr);
-    corsika::stack::SecondaryView view(particle);
+    setup::StackView view(particle);
     auto projectile = view.GetProjectile();
     auto const projectileMomentum = projectile.GetMomentum();
 
     Interaction model;
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
+    [[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));
diff --git a/Processes/Sibyll/CMakeLists.txt b/Processes/Sibyll/CMakeLists.txt
index 915aef2587a99c989ab635ec49712c9c46febdd1..856135c789ff02fa40fd77b3e18d93f68a8e3dfa 100644
--- a/Processes/Sibyll/CMakeLists.txt
+++ b/Processes/Sibyll/CMakeLists.txt
@@ -79,6 +79,7 @@ target_link_libraries (
   CORSIKAenvironment
   CORSIKAsetup
   CORSIKArandom
+  CORSIKAlogging
   )
 
 target_include_directories (
diff --git a/Processes/Sibyll/Decay.cc b/Processes/Sibyll/Decay.cc
index d1a0c0cb1aff2d14a7ca98f8f5b2422af83de80a..62d0b45819be56c8fcbec7b67360a17c96650b73 100644
--- a/Processes/Sibyll/Decay.cc
+++ b/Processes/Sibyll/Decay.cc
@@ -14,8 +14,7 @@
 #include <corsika/setup/SetupStack.h>
 #include <corsika/setup/SetupTrajectory.h>
 
-using std::cout;
-using std::endl;
+using std::make_tuple;
 using std::tuple;
 using std::vector;
 
@@ -41,7 +40,7 @@ namespace corsika::process::sibyll {
     SetAllStable();
   }
 
-  Decay::~Decay() { cout << "Sibyll::Decay n=" << fCount << endl; }
+  Decay::~Decay() { C8LOG_DEBUG(fmt::format("Sibyll::Decay n={}", fCount)); }
 
   bool Decay::CanHandleDecay(const particles::Code vParticleCode) {
     using namespace corsika::particles;
@@ -61,7 +60,7 @@ namespace corsika::process::sibyll {
 
   void Decay::SetHandleDecay(const particles::Code vParticleCode) {
     handleAllDecays_ = false;
-    cout << "Sibyll::Decay: set to handle decay of " << vParticleCode << endl;
+    C8LOG_DEBUG(fmt::format("Sibyll::Decay: set to handle decay of {}", vParticleCode));
     if (Decay::CanHandleDecay(vParticleCode))
       handledDecays_.insert(vParticleCode);
     else
@@ -103,13 +102,13 @@ namespace corsika::process::sibyll {
   }
 
   void Decay::SetUnstable(const particles::Code vCode) {
-    cout << "Sibyll::Decay: setting " << vCode << " unstable.." << endl;
+    C8LOG_DEBUG(fmt::format("Sibyll::Decay: setting {} unstable. ", vCode));
     const int s_id = abs(process::sibyll::ConvertToSibyllRaw(vCode));
     s_csydec_.idb[s_id - 1] = abs(s_csydec_.idb[s_id - 1]);
   }
 
   void Decay::SetStable(const particles::Code vCode) {
-    cout << "Sibyll::Decay: setting " << vCode << " stable.." << endl;
+    C8LOG_DEBUG(fmt::format("Sibyll::Decay: setting {} stable. ", vCode));
     const int s_id = abs(process::sibyll::ConvertToSibyllRaw(vCode));
     s_csydec_.idb[s_id - 1] = (-1) * abs(s_csydec_.idb[s_id - 1]);
   }
@@ -123,23 +122,23 @@ namespace corsika::process::sibyll {
   }
 
   void Decay::PrintDecayConfig(const particles::Code vCode) {
-    cout << "Decay: Sibyll decay configuration:" << endl;
     const int sibCode = process::sibyll::ConvertToSibyllRaw(vCode);
     const int absSibCode = abs(sibCode);
-    cout << vCode << " is ";
-    if (s_csydec_.idb[absSibCode - 1] <= 0)
-      cout << "stable" << endl;
-    else
-      cout << "unstable" << endl;
+    C8LOG_DEBUG(
+        fmt::format("Decay: Sibyll decay configuration: {} is {}", vCode,
+                    (s_csydec_.idb[absSibCode - 1] <= 0) ? "stable" : "unstable"));
   }
 
   void Decay::PrintDecayConfig() {
-    cout << "Sibyll::Decay: decay configuration:" << endl;
-    if (handleAllDecays_)
-      cout << " all particles known to Sibyll are handled by Sibyll::Decay!" << endl;
-    else
-      for (auto& pCode : handledDecays_)
-        cout << "Decay of " << pCode << " is handled by Sibyll!" << endl;
+    C8LOG_DEBUG(fmt::format("Sibyll::Decay: decay configuration:"));
+    if (handleAllDecays_) {
+      C8LOG_DEBUG(fmt::format(
+          "     all particles known to Sibyll are handled by Sibyll::Decay!"));
+    } else {
+      for (auto& pCode : handledDecays_) {
+        C8LOG_DEBUG(fmt::format("      Decay of {}  is handled by Sibyll!", pCode));
+      }
+    }
   }
 
   template <>
@@ -158,17 +157,17 @@ namespace corsika::process::sibyll {
 
       const auto mkin =
           (E * E - vP.GetMomentum().squaredNorm()); // delta_mass(vP.GetMomentum(), E, m);
-      cout << "Sibyll::Decay: code: " << vP.GetPID() << endl;
-      cout << "Sibyll::Decay: MinStep: t0: " << t0 << endl;
-      cout << "Sibyll::Decay: MinStep: energy: " << E / 1_GeV << " GeV" << endl;
-      cout << "Sibyll::Decay: momentum: " << vP.GetMomentum().GetComponents() / 1_GeV
-           << " GeV" << endl;
-      cout << "Sibyll::Decay: momentum: shell mass-kin. inv. mass "
-           << mkin / 1_GeV / 1_GeV << " " << m / 1_GeV * m / 1_GeV << endl;
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: code: {} ", vP.GetPID()));
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: MinStep: t0: {} ", t0));
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: MinStep: energy: {} GeV ", E / 1_GeV));
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: momentum: {} GeV ",
+                              vP.GetMomentum().GetComponents() / 1_GeV));
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: momentum: shell mass-kin. inv. mass {} {}",
+                              mkin / 1_GeV / 1_GeV, m / 1_GeV * m / 1_GeV));
       auto sib_id = process::sibyll::ConvertToSibyllRaw(vP.GetPID());
-      cout << "Sibyll::Decay: sib mass: " << get_sibyll_mass2(sib_id) << endl;
-      cout << "Sibyll::Decay: MinStep: gamma: " << gamma << endl;
-      cout << "Sibyll::Decay: MinStep: tau (s): " << lifetime / 1_s << endl;
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: sib mass: {}", get_sibyll_mass2(sib_id)));
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: MinStep: gamma:  {}", gamma));
+      C8LOG_DEBUG(fmt::format("Sibyll::Decay: MinStep: tau {} s: ", lifetime / 1_s));
 
       return lifetime;
     } else
@@ -176,11 +175,13 @@ namespace corsika::process::sibyll {
   }
 
   template <>
-  void Decay::DoDecay(SetupProjectile& vP) {
+  void Decay::DoDecay(SetupView& view) {
     using geometry::Point;
     using namespace units::si;
 
-    const particles::Code pCode = vP.GetPID();
+    auto const projectile = view.GetProjectile();
+
+    const particles::Code pCode = projectile.GetPID();
     // check if sibyll is configured to handle this decay!
     if (!IsDecayHandled(pCode))
       throw std::runtime_error("STOP! Sibyll not configured to execute this decay!");
@@ -190,14 +191,14 @@ namespace corsika::process::sibyll {
     ss.Clear();
 
     // copy particle to sibyll stack
-    ss.AddParticle(process::sibyll::ConvertToSibyllRaw(pCode), vP.GetEnergy(),
-                   vP.GetMomentum(),
+    ss.AddParticle(process::sibyll::ConvertToSibyllRaw(pCode), projectile.GetEnergy(),
+                   projectile.GetMomentum(),
                    // setting particle mass with Corsika values, may be inconsistent
                    // with sibyll internal values
                    particles::GetMass(pCode));
     // remember position
-    Point const decayPoint = vP.GetPosition();
-    TimeType const t0 = vP.GetTime();
+    Point const decayPoint = projectile.GetPosition();
+    TimeType const t0 = projectile.GetTime();
     // remember if particles is unstable
     // auto const priorIsUnstable = IsUnstable(pCode);
     // switch on decay for this particle
@@ -205,7 +206,7 @@ namespace corsika::process::sibyll {
     PrintDecayConfig(pCode);
 
     // call sibyll decay
-    cout << "Decay: calling Sibyll decay routine.." << endl;
+    C8LOG_DEBUG(fmt::format("Decay: calling Sibyll decay routine.."));
     decsib_();
 
     // print output
@@ -220,11 +221,8 @@ namespace corsika::process::sibyll {
       // FOR NOW: skip particles that have decayed in Sibyll, move to iterator?
       if (psib.HasDecayed()) continue;
       // add to corsika stack
-      vP.AddSecondary(
-          tuple<particles::Code, units::si::HEPEnergyType, corsika::stack::MomentumVector,
-                geometry::Point, units::si::TimeType>{
-              process::sibyll::ConvertFromSibyll(psib.GetPID()), psib.GetEnergy(),
-              psib.GetMomentum(), decayPoint, t0});
+      view.AddSecondary(make_tuple(process::sibyll::ConvertFromSibyll(psib.GetPID()),
+                                   psib.GetEnergy(), psib.GetMomentum(), decayPoint, t0));
     }
     // empty sibyll stack
     ss.Clear();
diff --git a/Processes/Sibyll/Decay.h b/Processes/Sibyll/Decay.h
index 85e62273741ba784ada7efee5a2b685702abf357..42867ed2b560487901489c0df1bb153380948147 100644
--- a/Processes/Sibyll/Decay.h
+++ b/Processes/Sibyll/Decay.h
@@ -52,8 +52,8 @@ namespace corsika::process {
        In this function SIBYLL is called to produce to decay the input particle.
      */
 
-      template <typename TProjectile>
-      void DoDecay(TProjectile&);
+      template <typename TSecondaryView>
+      void DoDecay(TSecondaryView&);
 
       template <typename TParticleView>
       EProcessReturn DoSecondaries(TParticleView&);
diff --git a/Processes/Sibyll/Interaction.cc b/Processes/Sibyll/Interaction.cc
index d738dc81caa233416e74b1ca56d5e6e3d5662473..b028ca8769e3761c8adf6453b6162478271a756e 100644
--- a/Processes/Sibyll/Interaction.cc
+++ b/Processes/Sibyll/Interaction.cc
@@ -20,14 +20,13 @@
 
 #include <tuple>
 
-using std::cout;
-using std::endl;
+using std::make_tuple;
 using std::tuple;
 
 using namespace corsika;
 using namespace corsika::setup;
 using SetupParticle = setup::Stack::StackIterator;
-using SetupProjectile = setup::StackView::StackIterator;
+using SetupView = setup::StackView;
 using Track = Trajectory;
 
 namespace corsika::process::sibyll {
@@ -45,7 +44,7 @@ namespace corsika::process::sibyll {
   }
 
   Interaction::~Interaction() {
-    cout << "Sibyll::Interaction n=" << count_ << " Nnuc=" << nucCount_ << endl;
+    C8LOG_DEBUG(fmt::format("Sibyll::Interaction n={}, Nnuc={}", count_, nucCount_));
   }
 
   void Interaction::SetAllStable() {
@@ -112,10 +111,12 @@ namespace corsika::process::sibyll {
     const HEPEnergyType ECoM = sqrt(
         (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy
 
-    cout << "Interaction: LambdaInt: \n"
-         << " input energy: " << vP.GetEnergy() / 1_GeV << endl
-         << " beam can interact:" << kInteraction << endl
-         << " beam pid:" << vP.GetPID() << endl;
+    C8LOG_DEBUG(
+        fmt::format("Interaction: LambdaInt: \n"
+                    " input energy: {} GeV "
+                    " beam can interact: {} "
+                    " beam pid: {}",
+                    vP.GetEnergy() / 1_GeV, kInteraction, vP.GetPID()));
 
     // TODO: move limits into variables
     // FR: removed && Elab >= 8.5_GeV
@@ -137,16 +138,18 @@ namespace corsika::process::sibyll {
             return std::get<0>(this->GetCrossSection(corsikaBeamId, targetID, ECoM));
           });
 
-      cout << "Interaction: "
-           << "IntLength: weighted CrossSection (mb): " << weightedProdCrossSection / 1_mb
-           << endl;
+      C8LOG_DEBUG(
+          fmt::format("Interaction: "
+                      "IntLength: weighted CrossSection (mb): {} ",
+                      weightedProdCrossSection / 1_mb));
 
       // 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;
+      C8LOG_DEBUG(
+          fmt::format("Interaction: "
+                      "interaction length (g/cm2): {} ",
+                      int_length / (0.001_kg) * 1_cm * 1_cm));
 
       return int_length;
     }
@@ -160,16 +163,19 @@ namespace corsika::process::sibyll {
    */
 
   template <>
-  process::EProcessReturn Interaction::DoInteraction(SetupProjectile& vP) {
+  process::EProcessReturn Interaction::DoInteraction(SetupView& view) {
     using namespace utl;
     using namespace units;
     using namespace units::si;
     using namespace geometry;
 
-    const auto corsikaBeamId = vP.GetPID();
-    cout << "ProcessSibyll: "
-         << "DoInteraction: " << corsikaBeamId << " interaction? "
-         << process::sibyll::CanInteract(corsikaBeamId) << endl;
+    auto const projectile = view.GetProjectile();
+
+    const auto corsikaBeamId = projectile.GetPID();
+    C8LOG_DEBUG(
+        fmt::format("ProcessSibyll: "
+                    "DoInteraction: {} interaction? ",
+                    corsikaBeamId, process::sibyll::CanInteract(corsikaBeamId)));
 
     if (particles::IsNucleus(corsikaBeamId)) {
       // nuclei handled by different process, this should not happen
@@ -178,12 +184,12 @@ namespace corsika::process::sibyll {
 
     if (process::sibyll::CanInteract(corsikaBeamId)) {
       // position and time of interaction, not used in Sibyll
-      Point const pOrig = vP.GetPosition();
-      TimeType const tOrig = vP.GetTime();
+      Point const pOrig = projectile.GetPosition();
+      TimeType const tOrig = projectile.GetTime();
 
       // define projectile
-      HEPEnergyType const eProjectileLab = vP.GetEnergy();
-      auto const pProjectileLab = vP.GetMomentum();
+      HEPEnergyType const eProjectileLab = projectile.GetEnergy();
+      auto const pProjectileLab = projectile.GetMomentum();
       const CoordinateSystem& originalCS = pProjectileLab.GetCoordinateSystem();
 
       // define target
@@ -193,11 +199,14 @@ namespace corsika::process::sibyll {
       const auto pTargetLab = MomentumVector(originalCS, 0_GeV, 0_GeV, 0_GeV);
       const FourVector PtargLab(eTargetLab, pTargetLab);
 
-      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;
+      C8LOG_DEBUG(
+          fmt::format("Interaction: ebeam lab: {} GeV"
+                      "Interaction: pbeam lab: {} GeV",
+                      eProjectileLab / 1_GeV, pProjectileLab.GetComponents()));
+      C8LOG_DEBUG(
+          fmt::format("Interaction: etarget lab: {} GeV "
+                      "Interaction: ptarget lab: {} GeV",
+                      eTargetLab / 1_GeV, pTargetLab.GetComponents() / 1_GeV));
 
       const FourVector PprojLab(eProjectileLab, pProjectileLab);
 
@@ -214,25 +223,28 @@ namespace corsika::process::sibyll {
       // boost target
       auto const PtargCoM = boost.toCoM(PtargLab);
 
-      cout << "Interaction: ebeam CoM: " << PprojCoM.GetTimeLikeComponent() / 1_GeV
-           << endl
-           << "Interaction: pbeam CoM: "
-           << PprojCoM.GetSpaceLikeComponents().GetComponents(csPrime) / 1_GeV << endl;
-      cout << "Interaction: etarget CoM: " << PtargCoM.GetTimeLikeComponent() / 1_GeV
-           << endl
-           << "Interaction: ptarget CoM: "
-           << PtargCoM.GetSpaceLikeComponents().GetComponents(csPrime) / 1_GeV << endl;
-
-      cout << "Interaction: position of interaction: " << pOrig.GetCoordinates() << endl;
-      cout << "Interaction: time: " << tOrig << endl;
+      C8LOG_DEBUG(
+          fmt::format("Interaction: ebeam CoM: {} GeV "
+                      "Interaction: pbeam CoM: {} GeV ",
+                      PprojCoM.GetTimeLikeComponent() / 1_GeV,
+                      PprojCoM.GetSpaceLikeComponents().GetComponents(csPrime) / 1_GeV));
+      C8LOG_DEBUG(
+          fmt::format("Interaction: etarget CoM: {} GeV "
+                      "Interaction: ptarget CoM: {} GeV ",
+                      PtargCoM.GetTimeLikeComponent() / 1_GeV,
+                      PtargCoM.GetSpaceLikeComponents().GetComponents(csPrime) / 1_GeV));
+
+      C8LOG_DEBUG(fmt::format("Interaction: position of interaction: {} ",
+                              pOrig.GetCoordinates()));
+      C8LOG_DEBUG(fmt::format("Interaction: time: {} ", tOrig));
 
       HEPEnergyType Etot = eProjectileLab + eTargetLab;
-      MomentumVector Ptot = vP.GetMomentum();
+      MomentumVector Ptot = projectile.GetMomentum();
       // invariant mass, i.e. cm. energy
       HEPEnergyType Ecm = sqrt(Etot * Etot - Ptot.squaredNorm());
 
       // sample target mass number
-      auto const* currentNode = vP.GetNode();
+      auto const* currentNode = projectile.GetNode();
       auto const& mediumComposition =
           currentNode->GetModelProperties().GetNuclearComposition();
       // get cross sections for target materials
@@ -253,7 +265,7 @@ namespace corsika::process::sibyll {
 
       const auto targetCode =
           mediumComposition.SampleTarget(cross_section_of_components, RNG_);
-      cout << "Interaction: target selected: " << targetCode << endl;
+      C8LOG_DEBUG(fmt::format("Interaction: target selected: {} ", targetCode));
       /*
         FOR NOW: allow nuclei with A<18 or protons only.
         when medium composition becomes more complex, approximations will have to be
@@ -262,7 +274,7 @@ namespace corsika::process::sibyll {
       int targetSibCode = -1;
       if (IsNucleus(targetCode)) targetSibCode = GetNucleusA(targetCode);
       if (targetCode == particles::Proton::GetCode()) targetSibCode = 1;
-      cout << "Interaction: sibyll code: " << targetSibCode << endl;
+      C8LOG_DEBUG(fmt::format("Interaction: sibyll code: {}", targetSibCode));
       if (targetSibCode > maxTargetMassNumber_ || targetSibCode < 1)
         throw std::runtime_error(
             "Sibyll target outside range. Only nuclei with A<18 or protons are "
@@ -271,16 +283,19 @@ namespace corsika::process::sibyll {
       // beam id for sibyll
       const int kBeam = process::sibyll::ConvertToSibyllRaw(corsikaBeamId);
 
-      cout << "Interaction: "
-           << " DoInteraction: E(GeV):" << eProjectileLab / 1_GeV
-           << " Ecm(GeV): " << Ecm / 1_GeV << endl;
+      C8LOG_DEBUG(
+          fmt::format("Interaction: "
+                      " DoInteraction: E(GeV): {} "
+                      " Ecm(GeV): {} ",
+                      eProjectileLab / 1_GeV, Ecm / 1_GeV));
       if (Ecm > GetMaxEnergyCoM())
         throw std::runtime_error("Interaction::DoInteraction: CoM energy too high!");
       // FR: removed eProjectileLab < 8.5_GeV ||
       if (Ecm < GetMinEnergyCoM()) {
-        cout << "Interaction: "
-             << " DoInteraction: should have dropped particle.. "
-             << "THIS IS AN ERROR" << endl;
+        C8LOG_DEBUG(
+            fmt::format("Interaction: "
+                        " DoInteraction: should have dropped particle.. "
+                        "THIS IS AN ERROR"));
         throw std::runtime_error("energy too low for SIBYLL");
       } else {
         count_++;
@@ -316,25 +331,27 @@ namespace corsika::process::sibyll {
           assert(p3lab.GetCoordinateSystem() == originalCS); // just to be sure!
 
           // add to corsika stack
-          auto pnew = vP.AddSecondary(
-              tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
-                    geometry::Point, units::si::TimeType>{
-                  process::sibyll::ConvertFromSibyll(psib.GetPID()),
-                  Plab.GetTimeLikeComponent(), p3lab, pOrig, tOrig});
+          auto pnew = view.AddSecondary(
+              make_tuple(process::sibyll::ConvertFromSibyll(psib.GetPID()),
+                         Plab.GetTimeLikeComponent(), p3lab, pOrig, tOrig));
 
           Plab_final += pnew.GetMomentum();
           Elab_final += pnew.GetEnergy();
           Ecm_final += psib.GetEnergy();
         }
-        cout << "conservation (all GeV):" << endl
-             << "Ecm_initial(per nucleon)=" << Ecm / 1_GeV << " Ecm_final(per nucleon)="
-             << Ecm_final * 2. / (get_nwounded() + 1) / 1_GeV << endl
-             << "Elab_initial=" << Etot / 1_GeV << " Elab_final=" << Elab_final / 1_GeV
-             << " diff (%)=" << (Elab_final / Etot / get_nwounded() - 1) * 100
-             << " E in nucleons=" << constants::nucleonMass * get_nwounded() / 1_GeV
-             << endl
-             << "Plab_initial=" << (pProjectileLab / 1_GeV).GetComponents()
-             << ", Plab_final=" << (Plab_final / 1_GeV).GetComponents() << endl;
+        C8LOG_DEBUG(fmt::format(
+            "conservation (all GeV):"
+            "Ecm_initial(per nucleon)={}, Ecm_final(per nucleon)={}, "
+            "Elab_initial={}, Elab_final={}, "
+            "diff (%)={}, "
+            "E in nucleons={}, "
+            "Plab_initial={}, "
+            "Plab_final={} ",
+            Ecm / 1_GeV, Ecm_final * 2. / (get_nwounded() + 1) / 1_GeV, Etot / 1_GeV,
+            Elab_final / 1_GeV, (Elab_final / Etot / get_nwounded() - 1) * 100,
+            constants::nucleonMass * get_nwounded() / 1_GeV,
+            (pProjectileLab / 1_GeV).GetComponents(),
+            (Plab_final / 1_GeV).GetComponents()));
       }
     }
     return process::EProcessReturn::eOk;
diff --git a/Processes/Sibyll/Interaction.h b/Processes/Sibyll/Interaction.h
index fcb3b94e5f342134b16289286e66ee977d157abf..cccc971bd36517f955f23da88d6b7a588fd4341d 100644
--- a/Processes/Sibyll/Interaction.h
+++ b/Processes/Sibyll/Interaction.h
@@ -52,8 +52,8 @@ namespace corsika::process::sibyll {
        event is copied (and boosted) into the shower lab frame.
      */
 
-    template <typename TProjectile>
-    corsika::process::EProcessReturn DoInteraction(TProjectile&);
+    template <typename TSecondaryView>
+    corsika::process::EProcessReturn DoInteraction(TSecondaryView&);
 
   private:
     corsika::random::RNG& RNG_ =
diff --git a/Processes/Sibyll/NuclearInteraction.cc b/Processes/Sibyll/NuclearInteraction.cc
index 451a5c643478a54db7199a0ef919ddc464cacb5c..c53beeae7a6934dd090af9ea5302deacf342b681 100644
--- a/Processes/Sibyll/NuclearInteraction.cc
+++ b/Processes/Sibyll/NuclearInteraction.cc
@@ -19,24 +19,27 @@
 #include <corsika/setup/SetupStack.h>
 #include <corsika/setup/SetupTrajectory.h>
 
+#include <corsika/logging/Logging.h>
+
 #include <set>
+#include <sstream>
 
-using std::cout;
-using std::endl;
+using std::make_tuple;
 using std::tuple;
 using std::vector;
 
 using namespace corsika;
 using namespace corsika::setup;
-using Particle = Stack::ParticleType;        // StackIterator; // ParticleType;
-using Projectile = StackView::StackIterator; // StackView::ParticleType;
+using Particle = corsika::setup::Stack::ParticleType; // StackIterator; // ParticleType;
+using View = corsika::setup::StackView;               // StackView::ParticleType;
 using Track = Trajectory;
 
 namespace corsika::process::sibyll {
 
   template <>
   NuclearInteraction<SetupEnvironment>::~NuclearInteraction() {
-    cout << "Nuclib::NuclearInteraction n=" << count_ << " Nnuc=" << nucCount_ << endl;
+    C8LOG_DEBUG(
+        fmt::format("Nuclib::NuclearInteraction n={} Nnuc={}", count_, nucCount_));
   }
 
   template <>
@@ -46,20 +49,22 @@ namespace corsika::process::sibyll {
     const int k = targetComponentsIndex_.at(pCode);
     Code pNuclei[] = {Code::Helium, Code::Lithium7, Code::Oxygen,
                       Code::Neon,   Code::Argon,    Code::Iron};
-    cout << "en/A ";
-    for (auto& j : pNuclei) cout << std::setw(9) << j;
-    cout << endl;
+    std::ostringstream table;
+    table << "Nuclear CrossSectionTable pCode=" << pCode << " :\n en/A ";
+    for (auto& j : pNuclei) table << std::setw(9) << j;
+    table << "\n";
 
     // loop over energy bins
-    for (int i = 0; i < GetNEnergyBins(); ++i) {
-      cout << " " << i << "  ";
+    for (unsigned int i = 0; i < GetNEnergyBins(); ++i) {
+      table << " " << i << "  ";
       for (auto& n : pNuclei) {
         auto const j = GetNucleusA(n);
-        cout << " " << std::setprecision(5) << std::setw(8)
-             << cnucsignuc_.sigma[j - 1][k][i];
+        table << " " << std::setprecision(5) << std::setw(8)
+              << cnucsignuc_.sigma[j - 1][k][i];
       }
-      cout << endl;
+      table << "\n";
     }
+    C8LOG_DEBUG(table.str());
   }
 
   template <>
@@ -82,23 +87,24 @@ namespace corsika::process::sibyll {
       return allElementsInUniverse;
     });
 
-    cout << "NuclearInteraction: initializing nuclear cross sections..." << endl;
+    C8LOG_DEBUG("NuclearInteraction: initializing nuclear cross sections...");
 
     // loop over target components, at most 4!!
     int k = -1;
     for (auto& ptarg : allElementsInUniverse) {
       ++k;
-      cout << "NuclearInteraction: init target component: " << ptarg << endl;
+      C8LOG_DEBUG(fmt::format("NuclearInteraction: init target component: {}", ptarg));
       const int ib = GetNucleusA(ptarg);
       if (!hadronicInteraction_.IsValidTarget(ptarg)) {
-        cout << "NuclearInteraction::InitializeNuclearCrossSections: target nucleus? id="
-             << ptarg << endl;
+        C8LOG_DEBUG(fmt::format(
+            "NuclearInteraction::InitializeNuclearCrossSections: target nucleus? id={}",
+            ptarg));
         throw std::runtime_error(
             " target can not be handled by hadronic interaction model! ");
       }
       targetComponentsIndex_.insert(std::pair<Code, int>(ptarg, k));
       // loop over energies, fNEnBins log. energy bins
-      for (int i = 0; i < GetNEnergyBins(); ++i) {
+      for (unsigned int i = 0; i < GetNEnergyBins(); ++i) {
         // hard coded energy grid, has to be aligned to definition in signuc2!!, no
         // comment..
         const units::si::HEPEnergyType Ecm = pow(10., 1. + 1. * i) * 1_GeV;
@@ -109,7 +115,7 @@ namespace corsika::process::sibyll {
         const double dsig = siginel / 1_mb;
         const double dsigela = sigela / 1_mb;
         // loop over projectiles, mass numbers from 2 to fMaxNucleusAProjectile
-        for (int j = 1; j < gMaxNucleusAProjectile_; ++j) {
+        for (unsigned int j = 1; j < gMaxNucleusAProjectile_; ++j) {
           const int jj = j + 1;
           double sig_out, dsig_out, sigqe_out, dsigqe_out;
           sigma_mc_(jj, ib, dsig, dsigela, gNSample_, sig_out, dsig_out, sigqe_out,
@@ -120,12 +126,11 @@ namespace corsika::process::sibyll {
         }
       }
     }
-    cout << "NuclearInteraction: cross sections for " << targetComponentsIndex_.size()
-         << " components initialized!" << endl;
-    for (auto& ptarg : allElementsInUniverse) {
-      cout << "cross section table: " << ptarg << endl;
-      PrintCrossSectionTable(ptarg);
-    }
+    C8LOG_DEBUG(
+        fmt::format("NuclearInteraction: cross sections for {} "
+                    " components initialized!",
+                    targetComponentsIndex_.size()));
+    for (auto& ptarg : allElementsInUniverse) { PrintCrossSectionTable(ptarg); }
   }
 
   template <>
@@ -139,9 +144,9 @@ namespace corsika::process::sibyll {
       throw std::runtime_error("NuclearInteraction: energy outside tabulated range!");
     const double e0 = elabnuc / 1_GeV;
     double sig;
-    cout << "ReadCrossSectionTable: " << ia << " " << ib << " " << e0 << endl;
+    C8LOG_DEBUG(fmt::format("ReadCrossSectionTable: {} {} {}", ia, ib, e0));
     signuc2_(ia, ib, e0, sig);
-    cout << "ReadCrossSectionTable: sig=" << sig << endl;
+    C8LOG_DEBUG(fmt::format("ReadCrossSectionTable: sig={}", sig));
     return sig * 1_mb;
   }
 
@@ -156,19 +161,22 @@ namespace corsika::process::sibyll {
       throw std::runtime_error(
           "NuclearInteraction: GetCrossSection: particle not a nucleus!");
 
-    auto const iBeamA = vP.GetNuclearA();
+    const unsigned int iBeamA = vP.GetNuclearA();
     HEPEnergyType LabEnergyPerNuc = vP.GetEnergy() / iBeamA;
-    cout << "NuclearInteraction: GetCrossSection: called with: beamNuclA= " << iBeamA
-         << " TargetId= " << TargetId << " LabEnergyPerNuc= " << LabEnergyPerNuc / 1_GeV
-         << endl;
+    C8LOG_DEBUG(
+        fmt::format("NuclearInteraction: GetCrossSection: called with: beamNuclA={} "
+                    " TargetId={} LabEnergyPerNuc={}GeV ",
+                    iBeamA, TargetId, LabEnergyPerNuc / 1_GeV));
 
     // use nuclib to calc. nuclear cross sections
     // TODO: for now assumes air with hard coded composition
     // extend to arbitrary mixtures, requires smarter initialization
     // get nuclib projectile code: nucleon number
     if (iBeamA > GetMaxNucleusAProjectile() || iBeamA < 2) {
-      cout << "NuclearInteraction: beam nucleus outside allowed range for NUCLIB!" << endl
-           << "A=" << iBeamA << endl;
+      C8LOG_DEBUG(
+          "NuclearInteraction: beam nucleus outside allowed range for NUCLIB!"
+          "A=" +
+          std::to_string(iBeamA));
       throw std::runtime_error(
           "NuclearInteraction: GetCrossSection: beam nucleus outside allowed range for "
           "NUCLIB!");
@@ -176,7 +184,7 @@ namespace corsika::process::sibyll {
 
     if (hadronicInteraction_.IsValidTarget(TargetId)) {
       auto const sigProd = ReadCrossSectionTable(iBeamA, TargetId, LabEnergyPerNuc);
-      cout << "cross section (mb): " << sigProd / 1_mb << endl;
+      C8LOG_DEBUG("cross section (mb): " + std::to_string(sigProd / 1_mb));
       return std::make_tuple(sigProd, 0_mb);
     } else {
       throw std::runtime_error("target outside range.");
@@ -219,7 +227,7 @@ namespace corsika::process::sibyll {
 
     // total momentum and energy
     HEPEnergyType Elab = vP.GetEnergy() + constants::nucleonMass;
-    int const nuclA = vP.GetNuclearA();
+    unsigned int const nuclA = vP.GetNuclearA();
     auto const ElabNuc = vP.GetEnergy() / nuclA;
 
     corsika::stack::MomentumVector pTotLab(rootCS, {0.0_GeV, 0.0_GeV, 0.0_GeV});
@@ -230,13 +238,16 @@ namespace corsika::process::sibyll {
     const HEPEnergyType ECoM = sqrt(
         (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy
     auto const ECoMNN = sqrt(2. * ElabNuc * constants::nucleonMass);
-    cout << "NuclearInteraction: LambdaInt: \n"
-         << " input energy: " << Elab / 1_GeV << endl
-         << " input energy CoM: " << ECoM / 1_GeV << endl
-         << " beam pid:" << corsikaBeamId << endl
-         << " beam A: " << nuclA << endl
-         << " input energy per nucleon: " << ElabNuc / 1_GeV << endl
-         << " input energy CoM per nucleon: " << ECoMNN / 1_GeV << endl;
+    C8LOG_DEBUG(
+        fmt::format("NuclearInteraction: LambdaInt: \n"
+                    " input energy: {}GeV\n"
+                    " input energy CoM: {}GeV\n"
+                    " beam pid: {}\n"
+                    " beam A: {}\n"
+                    " input energy per nucleon: {}GeV\n"
+                    " input energy CoM per nucleon: {}GeV ",
+                    Elab / 1_GeV, ECoM / 1_GeV, particles::GetName(corsikaBeamId), nuclA,
+                    ElabNuc / 1_GeV, ECoMNN / 1_GeV));
     //      throw std::runtime_error("stop here");
 
     // energy limits
@@ -262,27 +273,30 @@ namespace corsika::process::sibyll {
       // loop over components in medium
       for (auto const targetId : mediumComposition.GetComponents()) {
         i++;
-        cout << "NuclearInteraction: get interaction length for target: " << targetId
-             << endl;
+        C8LOG_DEBUG("NuclearInteraction: get interaction length for target: " +
+                    particles::GetName(targetId));
         auto const [productionCrossSection, elaCrossSection] =
             GetCrossSection(vP, targetId);
         [[maybe_unused]] auto& dummy_elaCrossSection = elaCrossSection;
 
-        cout << "NuclearInteraction: "
-             << "IntLength: nuclib return (mb): " << productionCrossSection / 1_mb
-             << endl;
+        C8LOG_DEBUG(
+            "NuclearInteraction: "
+            "IntLength: nuclib return (mb): " +
+            std::to_string(productionCrossSection / 1_mb));
         weightedProdCrossSection += w[i] * productionCrossSection;
       }
-      cout << "NuclearInteraction: "
-           << "IntLength: weighted CrossSection (mb): " << weightedProdCrossSection / 1_mb
-           << endl;
+      C8LOG_DEBUG(
+          "NuclearInteraction: "
+          "IntLength: weighted CrossSection (mb): " +
+          std::to_string(weightedProdCrossSection / 1_mb));
 
       // calculate interaction length in medium
       GrammageType const int_length = mediumComposition.GetAverageMassNumber() *
                                       units::constants::u / weightedProdCrossSection;
-      cout << "NuclearInteraction: "
-           << "interaction length (g/cm2): " << int_length * (1_cm * 1_cm / (0.001_kg))
-           << endl;
+      C8LOG_DEBUG(
+          "NuclearInteraction: "
+          "interaction length (g/cm2): " +
+          std::to_string(int_length * (1_cm * 1_cm / (0.001_kg))));
 
       return int_length;
     } else {
@@ -293,7 +307,7 @@ namespace corsika::process::sibyll {
   template <>
   template <>
   process::EProcessReturn NuclearInteraction<SetupEnvironment>::DoInteraction(
-      Projectile& vP) {
+      View& view) {
 
     // this routine superimposes different nucleon-nucleon interactions
     // in a nucleus-nucleus interaction, based the SIBYLL routine SIBNUC
@@ -303,10 +317,13 @@ namespace corsika::process::sibyll {
     using namespace units::si;
     using namespace geometry;
 
-    const auto ProjId = vP.GetPID();
+    auto projectile = view.GetProjectile();
+
+    const auto ProjId = projectile.GetPID();
     // TODO: calculate projectile mass in nuclearStackExtension
-    //      const auto ProjMass = vP.GetMass();
-    cout << "NuclearInteraction: DoInteraction: called with:" << ProjId << endl;
+    //      const auto ProjMass = projectile.GetMass();
+    C8LOG_DEBUG("NuclearInteraction: DoInteraction: called with:" +
+                particles::GetName(ProjId));
 
     // check if target-style nucleus (enum)
     if (ProjId != particles::Code::Nucleus)
@@ -314,10 +331,11 @@ namespace corsika::process::sibyll {
           "NuclearInteraction: DoInteraction: Wrong nucleus type. Nuclear projectiles "
           "should use NuclearStackExtension!");
 
-    auto const ProjMass =
-        vP.GetNuclearZ() * particles::Proton::GetMass() +
-        (vP.GetNuclearA() - vP.GetNuclearZ()) * particles::Neutron::GetMass();
-    cout << "NuclearInteraction: projectile mass: " << ProjMass / 1_GeV << endl;
+    auto const ProjMass = projectile.GetNuclearZ() * particles::Proton::GetMass() +
+                          (projectile.GetNuclearA() - projectile.GetNuclearZ()) *
+                              particles::Neutron::GetMass();
+    C8LOG_DEBUG("NuclearInteraction: projectile mass: " +
+                std::to_string(ProjMass / 1_GeV));
 
     count_++;
 
@@ -325,35 +343,39 @@ namespace corsika::process::sibyll {
         RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
 
     // position and time of interaction, not used in NUCLIB
-    Point pOrig = vP.GetPosition();
-    TimeType tOrig = vP.GetTime();
+    Point pOrig = projectile.GetPosition();
+    TimeType tOrig = projectile.GetTime();
 
-    cout << "Interaction: position of interaction: " << pOrig.GetCoordinates() << endl;
-    cout << "Interaction: time: " << tOrig << endl;
+    C8LOG_DEBUG(
+        fmt::format("Interaction: position of interaction: {}", pOrig.GetCoordinates()));
+    C8LOG_DEBUG("Interaction: time: " + std::to_string(tOrig / 1_s));
 
     // projectile nucleon number
-    const int kAProj = vP.GetNuclearA();
+    const unsigned int kAProj = projectile.GetNuclearA();
     if (kAProj > GetMaxNucleusAProjectile())
       throw std::runtime_error("Projectile nucleus too large for NUCLIB!");
 
     // kinematics
     // define projectile nucleus
-    HEPEnergyType const eProjectileLab = vP.GetEnergy();
-    auto const pProjectileLab = vP.GetMomentum();
+    HEPEnergyType const eProjectileLab = projectile.GetEnergy();
+    auto const pProjectileLab = projectile.GetMomentum();
     const FourVector PprojLab(eProjectileLab, pProjectileLab);
 
-    cout << "NuclearInteraction: eProj lab: " << eProjectileLab / 1_GeV << endl
-         << "NuclearInteraction: pProj lab: " << pProjectileLab.GetComponents() / 1_GeV
-         << endl;
+    C8LOG_DEBUG(
+        fmt::format("NuclearInteraction: eProj lab: {} "
+                    "pProj lab: {} ",
+                    eProjectileLab / 1_GeV, pProjectileLab.GetComponents() / 1_GeV));
+    ;
 
     // define projectile nucleon
-    HEPEnergyType const eProjectileNucLab = vP.GetEnergy() / kAProj;
-    auto const pProjectileNucLab = vP.GetMomentum() / kAProj;
+    HEPEnergyType const eProjectileNucLab = projectile.GetEnergy() / kAProj;
+    auto const pProjectileNucLab = projectile.GetMomentum() / kAProj;
     const FourVector PprojNucLab(eProjectileNucLab, pProjectileNucLab);
 
-    cout << "NuclearInteraction: eProjNucleon lab: " << eProjectileNucLab / 1_GeV << endl
-         << "NuclearInteraction: pProjNucleon lab: "
-         << pProjectileNucLab.GetComponents() / 1_GeV << endl;
+    C8LOG_DEBUG(fmt::format(
+        "NuclearInteraction: eProjNucleon lab (GeV): {} "
+        "pProjNucleon lab (GeV): {}",
+        eProjectileNucLab / 1_GeV, pProjectileNucLab.GetComponents() / 1_GeV));
 
     // define target
     // always a nucleon
@@ -363,19 +385,21 @@ namespace corsika::process::sibyll {
         corsika::stack::MomentumVector(rootCS, 0_GeV, 0_GeV, 0_GeV);
     const FourVector PtargNucLab(eTargetNucLab, pTargetNucLab);
 
-    cout << "NuclearInteraction: etarget lab: " << eTargetNucLab / 1_GeV << endl
-         << "NuclearInteraction: ptarget lab: " << pTargetNucLab.GetComponents() / 1_GeV
-         << endl;
+    C8LOG_DEBUG(
+        fmt::format("NuclearInteraction: etarget lab(GeV): {}"
+                    "NuclearInteraction: ptarget lab(GeV): {} ",
+                    eTargetNucLab / 1_GeV, pTargetNucLab.GetComponents() / 1_GeV));
 
     // center-of-mass energy in nucleon-nucleon frame
     auto const PtotNN4 = PtargNucLab + PprojNucLab;
     HEPEnergyType EcmNN = PtotNN4.GetNorm();
-    cout << "NuclearInteraction: nuc-nuc cm energy: " << EcmNN / 1_GeV << endl;
+    C8LOG_DEBUG("NuclearInteraction: nuc-nuc cm energy: " +
+                std::to_string(EcmNN / 1_GeV));
 
     if (!hadronicInteraction_.IsValidCoMEnergy(EcmNN)) {
-      cout << "NuclearInteraction: nuc-nuc. CoM energy too low for hadronic "
-              "interaction model!"
-           << endl;
+      C8LOG_DEBUG(
+          "NuclearInteraction: nuc-nuc. CoM energy too low for hadronic "
+          "interaction model!");
       throw std::runtime_error("NuclearInteraction: DoInteraction: energy too low!");
     }
 
@@ -387,23 +411,25 @@ namespace corsika::process::sibyll {
     // boost target
     auto const PtargNucCoM = boost.toCoM(PtargNucLab);
 
-    cout << "Interaction: ebeam CoM: " << PprojNucCoM.GetTimeLikeComponent() / 1_GeV
-         << endl
-         << "Interaction: pbeam CoM: "
-         << PprojNucCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
-    cout << "Interaction: etarget CoM: " << PtargNucCoM.GetTimeLikeComponent() / 1_GeV
-         << endl
-         << "Interaction: ptarget CoM: "
-         << PtargNucCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+    C8LOG_DEBUG(
+        fmt::format("Interaction: ebeam CoM: {} "
+                    ", pbeam CoM: {}",
+                    PprojNucCoM.GetTimeLikeComponent() / 1_GeV,
+                    PprojNucCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV));
+    C8LOG_DEBUG(
+        fmt::format("Interaction: etarget CoM: {}"
+                    ", ptarget CoM: {}",
+                    PtargNucCoM.GetTimeLikeComponent() / 1_GeV,
+                    PtargNucCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV));
 
     // sample target nucleon number
     //
     // proton stand-in for nucleon
     const auto beamId = particles::Proton::GetCode();
-    auto const* const currentNode = vP.GetNode();
+    auto const* const currentNode = projectile.GetNode();
     const auto& mediumComposition =
         currentNode->GetModelProperties().GetNuclearComposition();
-    cout << "get nucleon-nucleus cross sections for target materials.." << endl;
+    C8LOG_DEBUG("get nucleon-nucleus cross sections for target materials..");
     // get cross sections for target materials
     // using nucleon-target-nucleus cross section!!!
     /*
@@ -415,8 +441,8 @@ namespace corsika::process::sibyll {
 
     for (size_t i = 0; i < compVec.size(); ++i) {
       auto const targetId = compVec[i];
-      cout << "target component: " << targetId << endl;
-      cout << "beam id: " << beamId << endl;
+      C8LOG_DEBUG("target component: " + particles::GetName(targetId));
+      C8LOG_DEBUG("beam id: " + particles::GetName(beamId));
       const auto [sigProd, sigEla] =
           hadronicInteraction_.GetCrossSection(beamId, targetId, EcmNN);
       cross_section_of_components[i] = sigProd;
@@ -425,7 +451,7 @@ namespace corsika::process::sibyll {
 
     const auto targetCode =
         mediumComposition.SampleTarget(cross_section_of_components, RNG_);
-    cout << "Interaction: target selected: " << targetCode << endl;
+    C8LOG_DEBUG("Interaction: target selected: " + particles::GetName(targetCode));
     /*
       FOR NOW: allow nuclei with A<18 or protons only.
       when medium composition becomes more complex, approximations will have to be
@@ -434,13 +460,13 @@ namespace corsika::process::sibyll {
     int kATarget = -1;
     if (IsNucleus(targetCode)) kATarget = GetNucleusA(targetCode);
     if (targetCode == particles::Proton::GetCode()) kATarget = 1;
-    cout << "NuclearInteraction: nuclib target code: " << kATarget << endl;
+    C8LOG_DEBUG("NuclearInteraction: nuclib target code: " + std::to_string(kATarget));
     if (!hadronicInteraction_.IsValidTarget(targetCode))
       throw std::runtime_error("target outside range. ");
     // end of target sampling
 
     // superposition
-    cout << "NuclearInteraction: sampling nuc. multiple interaction structure.. " << endl;
+    C8LOG_DEBUG("NuclearInteraction: sampling nuc. multiple interaction structure.. ");
     // get nucleon-nucleon cross section
     // (needed to determine number of nucleon-nucleon scatterings)
     const auto protonId = particles::Proton::GetCode();
@@ -452,24 +478,27 @@ namespace corsika::process::sibyll {
     // nuclear multiple scattering according to glauber (r.i.p.)
     int_nuc_(kATarget, kAProj, sigProd, sigEla);
 
-    cout << "number of nucleons in target           : " << kATarget << endl
-         << "number of wounded nucleons in target   : " << cnucms_.na << endl
-         << "number of nucleons in projectile       : " << kAProj << endl
-         << "number of wounded nucleons in project. : " << cnucms_.nb << endl
-         << "number of inel. nuc.-nuc. interactions : " << cnucms_.ni << endl
-         << "number of elastic nucleons in target   : " << cnucms_.nael << endl
-         << "number of elastic nucleons in project. : " << cnucms_.nbel << endl
-         << "impact parameter: " << cnucms_.b << endl;
+    C8LOG_DEBUG(
+        fmt::format("number of nucleons in target           : {}\n"
+                    "number of wounded nucleons in target   : {}\n"
+                    "number of nucleons in projectile       : {}\n"
+                    "number of wounded nucleons in project. : {}\n"
+                    "number of inel. nuc.-nuc. interactions : {}\n"
+                    "number of elastic nucleons in target   : {}\n"
+                    "number of elastic nucleons in project. : {}\n"
+                    "impact parameter: {}",
+                    kATarget, cnucms_.na, kAProj, cnucms_.nb, cnucms_.ni, cnucms_.nael,
+                    cnucms_.nbel, cnucms_.b));
 
     // calculate fragmentation
-    cout << "calculating nuclear fragments.." << endl;
+    C8LOG_DEBUG("calculating nuclear fragments..");
     // number of interactions
     // include elastic
     const int nElasticNucleons = cnucms_.nbel;
     const int nInelNucleons = cnucms_.nb;
     const int nIntProj = nInelNucleons + nElasticNucleons;
     const double impactPar = cnucms_.b; // only needed to avoid passing common var.
-    int nFragments;
+    int nFragments = 0;
     // number of fragments is limited to 60
     int AFragments[60];
     // call fragmentation routine
@@ -479,22 +508,22 @@ namespace corsika::process::sibyll {
     fragm_(kATarget, kAProj, nIntProj, impactPar, nFragments, AFragments);
 
     // this should not occur but well :)
-    if (nFragments > GetMaxNFragments())
+    if (nFragments > (int)GetMaxNFragments())
       throw std::runtime_error("Number of nuclear fragments in NUCLIB exceeded!");
 
-    cout << "number of fragments: " << nFragments << endl;
+    C8LOG_DEBUG("number of fragments: " + std::to_string(nFragments));
     for (int j = 0; j < nFragments; ++j)
-      cout << "fragment: " << j << " A=" << AFragments[j]
-           << " px=" << fragments_.ppp[j][0] << " py=" << fragments_.ppp[j][1]
-           << " pz=" << fragments_.ppp[j][2] << endl;
+      C8LOG_DEBUG(fmt::format("fragment {}: A={} px={} py={} pz={}", j, AFragments[j],
+                              fragments_.ppp[j][0], fragments_.ppp[j][1],
+                              fragments_.ppp[j][2]));
 
-    cout << "adding nuclear fragments to particle stack.." << endl;
+    C8LOG_DEBUG("adding nuclear fragments to particle stack..");
     // put nuclear fragments on corsika stack
     for (int j = 0; j < nFragments; ++j) {
       particles::Code specCode;
-      const auto nuclA = AFragments[j];
+      const int nuclA = AFragments[j];
       // get Z from stability line
-      const auto nuclZ = int(nuclA / 2.15 + 0.7);
+      const int nuclZ = int(nuclA / 2.15 + 0.7);
 
       // TODO: do we need to catch single nucleons??
       if (nuclA == 1)
@@ -508,41 +537,37 @@ namespace corsika::process::sibyll {
           particles::Proton::GetMass() * nuclZ +
           (nuclA - nuclZ) * particles::Neutron::GetMass(); // this neglects binding energy
 
-      cout << "NuclearInteraction: adding fragment: " << specCode << endl;
-      cout << "NuclearInteraction: A,Z: " << nuclA << "," << nuclZ << endl;
-      cout << "NuclearInteraction: mass: " << mass / 1_GeV << endl;
+      C8LOG_DEBUG("NuclearInteraction: adding fragment: " + particles::GetName(specCode));
+      C8LOG_DEBUG("NuclearInteraction: A,Z: " + std::to_string(nuclA) + ", " +
+                  std::to_string(nuclZ));
+      C8LOG_DEBUG("NuclearInteraction: mass: " + std::to_string(mass / 1_GeV));
 
       // CORSIKA 7 way
       // spectators inherit momentum from original projectile
       const double mass_ratio = mass / ProjMass;
 
-      cout << "NuclearInteraction: mass ratio " << mass_ratio << endl;
+      C8LOG_DEBUG("NuclearInteraction: mass ratio " + std::to_string(mass_ratio));
 
       auto const Plab = PprojLab * mass_ratio;
 
-      cout << "NuclearInteraction: fragment momentum: "
-           << Plab.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+      C8LOG_DEBUG(fmt::format("NuclearInteraction: fragment momentum: {}",
+                              Plab.GetSpaceLikeComponents().GetComponents() / 1_GeV));
 
       if (nuclA == 1)
         // add nucleon
-        vP.AddSecondary(
-            tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
-                  geometry::Point, units::si::TimeType>{
-                specCode, Plab.GetTimeLikeComponent(), Plab.GetSpaceLikeComponents(),
-                pOrig, tOrig});
+        projectile.AddSecondary(make_tuple(specCode, Plab.GetTimeLikeComponent(),
+                                           Plab.GetSpaceLikeComponents(), pOrig, tOrig));
       else
         // add nucleus
-        vP.AddSecondary(tuple<particles::Code, units::si::HEPEnergyType,
-                              corsika::stack::MomentumVector, geometry::Point,
-                              units::si::TimeType, unsigned short, unsigned short>{
-            specCode, Plab.GetTimeLikeComponent(), Plab.GetSpaceLikeComponents(), pOrig,
-            tOrig, nuclA, nuclZ});
+        projectile.AddSecondary(make_tuple(specCode, Plab.GetTimeLikeComponent(),
+                                           Plab.GetSpaceLikeComponents(), pOrig, tOrig,
+                                           nuclA, nuclZ));
     }
 
     // add elastic nucleons to corsika stack
     // TODO: the elastic interaction could be external like the inelastic interaction,
     // e.g. use existing ElasticModel
-    cout << "adding elastically scattered nucleons to particle stack.." << endl;
+    C8LOG_DEBUG("adding elastically scattered nucleons to particle stack..");
     for (int j = 0; j < nElasticNucleons; ++j) {
       // TODO: sample proton or neutron
       auto const elaNucCode = particles::Code::Proton;
@@ -553,31 +578,38 @@ namespace corsika::process::sibyll {
       const double mass_ratio = particles::GetMass(elaNucCode) / ProjMass;
       auto const Plab = PprojLab * mass_ratio;
 
-      vP.AddSecondary(
-          tuple<particles::Code, units::si::HEPEnergyType, corsika::stack::MomentumVector,
-                geometry::Point, units::si::TimeType>{
-              elaNucCode, Plab.GetTimeLikeComponent(), Plab.GetSpaceLikeComponents(),
-              pOrig, tOrig});
+      projectile.AddSecondary(make_tuple(elaNucCode, Plab.GetTimeLikeComponent(),
+                                         Plab.GetSpaceLikeComponents(), pOrig, tOrig));
     }
 
     // add inelastic interactions
-    cout << "calculate inelastic nucleon-nucleon interactions.." << endl;
+    C8LOG_DEBUG("calculate inelastic nucleon-nucleon interactions..");
     for (int j = 0; j < nInelNucleons; ++j) {
       // TODO: sample neutron or proton
       auto pCode = particles::Proton::GetCode();
       // temporarily add to stack, will be removed after interaction in DoInteraction
-      cout << "inelastic interaction no. " << j << endl;
-      auto inelasticNucleon = vP.AddSecondary(
-          tuple<particles::Code, units::si::HEPEnergyType, corsika::stack::MomentumVector,
-                geometry::Point, units::si::TimeType>{
-              pCode, PprojNucLab.GetTimeLikeComponent(),
-              PprojNucLab.GetSpaceLikeComponents(), pOrig, tOrig});
-      // create inelastic interaction
-      cout << "calling HadronicInteraction..." << endl;
-      hadronicInteraction_.DoInteraction(inelasticNucleon);
+      C8LOG_DEBUG(fmt::format("inelastic interaction no. {}", j));
+      setup::Stack nucleonStack;
+      // auto inelasticNucleon = projectile.AddSecondary(
+      auto inelasticNucleon = nucleonStack.AddParticle(
+          make_tuple(pCode, PprojNucLab.GetTimeLikeComponent(),
+                     PprojNucLab.GetSpaceLikeComponents(), pOrig, tOrig));
+      inelasticNucleon.SetNode(projectile.GetNode());
+      // create inelastic interaction for each nucleon
+      C8LOG_TRACE("calling HadronicInteraction...");
+      // create new StackView for each of the nucleons
+      View nucleon_secondaries(inelasticNucleon);
+      // all inner hadronic event generator
+      hadronicInteraction_.DoInteraction(nucleon_secondaries);
+      // inelasticNucleon.Delete(); // this is just a temporary object
+      for (const auto& pSec : nucleon_secondaries) {
+        projectile.AddSecondary(make_tuple(pSec.GetPID(), pSec.GetEnergy(),
+                                           pSec.GetMomentum(), pSec.GetPosition(),
+                                           pSec.GetTime()));
+      }
     }
 
-    cout << "NuclearInteraction: DoInteraction: done" << endl;
+    C8LOG_DEBUG("NuclearInteraction: DoInteraction: done");
 
     return process::EProcessReturn::eOk;
   }
diff --git a/Processes/Sibyll/NuclearInteraction.h b/Processes/Sibyll/NuclearInteraction.h
index ba9c710bb09363da5ee9f82d15bfeb6f41f397ab..4a561fad1298d78e8c45f03bd25711639366e1d6 100644
--- a/Processes/Sibyll/NuclearInteraction.h
+++ b/Processes/Sibyll/NuclearInteraction.h
@@ -41,9 +41,9 @@ namespace corsika::process::sibyll {
     corsika::units::si::HEPEnergyType GetMaxEnergyPerNucleonCoM() {
       return gMaxEnergyPerNucleonCoM_;
     }
-    int constexpr GetMaxNucleusAProjectile() { return gMaxNucleusAProjectile_; }
-    int constexpr GetMaxNFragments() { return gMaxNFragments_; }
-    int constexpr GetNEnergyBins() { return gNEnBins_; }
+    unsigned int constexpr GetMaxNucleusAProjectile() { return gMaxNucleusAProjectile_; }
+    unsigned int constexpr GetMaxNFragments() { return gMaxNFragments_; }
+    unsigned int constexpr GetNEnergyBins() { return gNEnBins_; }
 
     template <typename Particle>
     std::tuple<corsika::units::si::CrossSectionType, corsika::units::si::CrossSectionType>
@@ -52,8 +52,8 @@ namespace corsika::process::sibyll {
     template <typename Particle>
     corsika::units::si::GrammageType GetInteractionLength(Particle const&);
 
-    template <typename Projectile>
-    corsika::process::EProcessReturn DoInteraction(Projectile&);
+    template <typename TSecondaryView>
+    corsika::process::EProcessReturn DoInteraction(TSecondaryView&);
 
   private:
     TEnvironment const& environment_;
@@ -61,11 +61,11 @@ namespace corsika::process::sibyll {
     std::map<corsika::particles::Code, int> targetComponentsIndex_;
     corsika::random::RNG& RNG_ =
         corsika::random::RNGManager::GetInstance().GetRandomStream("sibyll");
-    static constexpr int gNSample_ =
+    static constexpr unsigned int gNSample_ =
         500; // number of samples in MC estimation of cross section
-    static constexpr int gMaxNucleusAProjectile_ = 56;
-    static constexpr int gNEnBins_ = 6;
-    static constexpr int gMaxNFragments_ = 60;
+    static constexpr unsigned int gMaxNucleusAProjectile_ = 56;
+    static constexpr unsigned int gNEnBins_ = 6;
+    static constexpr unsigned int gMaxNFragments_ = 60;
     // energy limits defined by table used for cross section in signuc.f
     // 10**1 GeV to 10**6 GeV
     static constexpr corsika::units::si::HEPEnergyType gMinEnergyPerNucleonCoM_ =
diff --git a/Processes/Sibyll/testSibyll.cc b/Processes/Sibyll/testSibyll.cc
index dee7ee31b722aaabdde09e864c0bbfe80d0b36b3..dae9741fcb3cba2641d6bd5a9ff885cb3752ed1c 100644
--- a/Processes/Sibyll/testSibyll.cc
+++ b/Processes/Sibyll/testSibyll.cc
@@ -19,6 +19,7 @@
 #include <corsika/units/PhysicalUnits.h>
 
 #include <catch2/catch.hpp>
+#include <tuple>
 
 using namespace corsika;
 using namespace corsika::process::sibyll;
@@ -87,9 +88,7 @@ using namespace corsika::units;
 template <typename TStackView>
 auto sumMomentum(TStackView const& view, geometry::CoordinateSystem const& vCS) {
   geometry::Vector<hepenergy_d> sum{vCS, 0_eV, 0_eV, 0_eV};
-
   for (auto const& p : view) { sum += p.GetMomentum(); }
-
   return sum;
 }
 
@@ -125,17 +124,14 @@ TEST_CASE("SibyllInterface", "[processes]") {
         sqrt(E0 * E0 - particles::Proton::GetMass() * particles::Proton::GetMass());
     auto plab = corsika::stack::MomentumVector(cs, {P0, 0_eV, 0_eV});
     geometry::Point pos(cs, 0_m, 0_m, 0_m);
-    auto particle = stack.AddParticle(
-        std::tuple<particles::Code, units::si::HEPEnergyType,
-                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            particles::Code::Proton, E0, plab, pos, 0_ns});
+    auto particle =
+        stack.AddParticle(std::make_tuple(particles::Code::Proton, E0, plab, pos, 0_ns));
     particle.SetNode(nodePtr);
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
+    corsika::setup::StackView view(particle);
 
     Interaction model;
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
     auto const pSum = sumMomentum(view, cs);
 
     /*
@@ -211,17 +207,14 @@ TEST_CASE("SibyllInterface", "[processes]") {
         sqrt(E0 * E0 - particles::Proton::GetMass() * particles::Proton::GetMass());
     auto plab = corsika::stack::MomentumVector(cs, {P0, 0_eV, 0_eV});
     geometry::Point pos(cs, 0_m, 0_m, 0_m);
-    auto particle = stack.AddParticle(
-        std::tuple<particles::Code, units::si::HEPEnergyType,
-                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            particles::Code::Proton, E0, plab, pos, 0_ns});
+    auto particle =
+        stack.AddParticle(std::make_tuple(particles::Code::Proton, E0, plab, pos, 0_ns));
     particle.SetNode(nodePtr);
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
+    corsika::setup::StackView view(particle);
 
     Interaction model;
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
     auto const pSum = sumMomentum(view, cs);
     CHECK(pSum.GetComponents(cs).GetX() / P0 == Approx(1).margin(0.001));
     CHECK(pSum.GetComponents(cs).GetY() / 1_GeV == Approx(0).margin(1e-4));
@@ -241,19 +234,15 @@ TEST_CASE("SibyllInterface", "[processes]") {
     auto plab = corsika::stack::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
     geometry::Point pos(cs, 0_m, 0_m, 0_m);
 
-    auto particle =
-        stack.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                     corsika::stack::MomentumVector, geometry::Point,
-                                     units::si::TimeType, unsigned short, unsigned short>{
-            particles::Code::Nucleus, E0, plab, pos, 0_ns, 4, 2});
+    auto particle = stack.AddParticle(
+        std::make_tuple(particles::Code::Nucleus, E0, plab, pos, 0_ns, 4, 2));
     particle.SetNode(nodePtr);
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
+    corsika::setup::StackView view(particle);
 
     Interaction hmodel;
     NuclearInteraction model(hmodel, env);
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
   }
 
@@ -265,12 +254,9 @@ TEST_CASE("SibyllInterface", "[processes]") {
         sqrt(E0 * E0 - particles::Proton::GetMass() * particles::Proton::GetMass());
     auto plab = corsika::stack::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
     geometry::Point pos(cs, 0_m, 0_m, 0_m);
-    auto particle = stack.AddParticle(
-        std::tuple<particles::Code, units::si::HEPEnergyType,
-                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            particles::Code::Lambda0, E0, plab, pos, 0_ns});
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
+    auto particle =
+        stack.AddParticle(std::make_tuple(particles::Code::Lambda0, E0, plab, pos, 0_ns));
+    corsika::setup::StackView view(particle);
 
     Decay model;
 
@@ -278,11 +264,11 @@ TEST_CASE("SibyllInterface", "[processes]") {
 
     [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
 
-    /*[[maybe_unused]] const process::EProcessReturn ret =*/model.DoDecay(projectile);
+    /*[[maybe_unused]] const process::EProcessReturn ret =*/model.DoDecay(view);
 
     // run checks
     // lambda decays into proton and pi- or neutron and pi+
-    CHECK(stack.GetSize() == 3);
+    CHECK(stack.getEntries() == 3);
   }
 
   SECTION("DecayConfiguration") {
diff --git a/Processes/StackInspector/StackInspector.cc b/Processes/StackInspector/StackInspector.cc
index af2270ecf32a185f66ae9464acf5885676fb7260..da4a91baafaf6253137956d57297cf198ba238c4 100644
--- a/Processes/StackInspector/StackInspector.cc
+++ b/Processes/StackInspector/StackInspector.cc
@@ -74,7 +74,7 @@ process::EProcessReturn StackInspector<TStack>::DoStack(const TStack& vS) {
        << " time=" << std::put_time(std::localtime(&now_time), "%T")
        << ", running=" << elapsed_seconds.count() << " seconds"
        << " (" << setw(3) << int(progress * 100) << "%)"
-       << ", nStep=" << GetStep() << ", stackSize=" << vS.GetSize()
+       << ", nStep=" << GetStep() << ", stackEntries=" << vS.getEntries()
        << ", Estack=" << Etot / 1_GeV << " GeV"
        << ", ETA=" << std::put_time(std::localtime(&eta_time), "%T") << endl;
   return process::EProcessReturn::eOk;
diff --git a/Processes/SwitchProcess/testSwitchProcess.cc b/Processes/SwitchProcess/testSwitchProcess.cc
index 0cff57f16fca233f3f75a38e22f006a4759341fe..9ee148e1855edc2b45c81594425e8786e2434d09 100644
--- a/Processes/SwitchProcess/testSwitchProcess.cc
+++ b/Processes/SwitchProcess/testSwitchProcess.cc
@@ -172,7 +172,7 @@ TEST_CASE("SwitchProcess from InteractionProcess") {
       InverseGrammageType invLambda = 0 / kgMSq;
       switchProcess.SelectInteraction(p, projectile, 0.01 / kgMSq, invLambda);
 
-      REQUIRE(view.GetSize() == 2);
+      REQUIRE(view.getSize() == 2);
     }
   }
 }
@@ -214,7 +214,7 @@ TEST_CASE("SwitchProcess from ProcessSequence") {
         InverseGrammageType accumulator = 0 / kgMSq;
         completeSeq.SelectInteraction(p, projectile, invLambda, accumulator);
 
-        numberOfSecondaries.push_back(view.GetSize());
+        numberOfSecondaries.push_back(view.getSize());
       }
 
       auto const mean =
@@ -248,7 +248,7 @@ TEST_CASE("SwitchProcess from ProcessSequence") {
         InverseGrammageType accumulator = 0 / kgMSq;
         completeSeq.SelectInteraction(p, projectile, invLambda, accumulator);
 
-        numberOfSecondaries.push_back(view.GetSize());
+        numberOfSecondaries.push_back(view.getSize());
       }
 
       auto const mean =
diff --git a/Processes/TrackingLine/testTrackingLineStack.h b/Processes/TrackingLine/testTrackingLineStack.h
index 1aeeecc10d0d17991a155c6fe55e062b50701fe5..5c714048a5eb1e3978f39f6815c80eaaebcdc8a0 100644
--- a/Processes/TrackingLine/testTrackingLineStack.h
+++ b/Processes/TrackingLine/testTrackingLineStack.h
@@ -9,23 +9,31 @@
 #pragma once
 
 #include <corsika/environment/Environment.h>
+
 #include <corsika/geometry/Point.h>
 #include <corsika/geometry/Vector.h>
+
 #include <corsika/particles/ParticleProperties.h>
-#include <corsika/setup/SetupStack.h>
+
+#include <corsika/stack/CombinedStack.h>
+#include <corsika/stack/node/GeometryNodeStackExtension.h>
+#include <corsika/stack/nuclear_extension/NuclearStackExtension.h>
+
 #include <corsika/units/PhysicalUnits.h>
 
 using TestEnvironmentType =
     corsika::environment::Environment<corsika::environment::Empty>;
 
 template <typename T>
-using SetupGeometryDataInterface = GeometryDataInterface<T, TestEnvironmentType>;
+using SetupGeometryDataInterface =
+    corsika::stack::node::GeometryDataInterface<T, TestEnvironmentType>;
 
 // combine particle data stack with geometry information for tracking
 template <typename StackIter>
 using StackWithGeometryInterface = corsika::stack::CombinedParticleInterface<
-    corsika::setup::detail::ParticleDataStack::PIType, SetupGeometryDataInterface,
-    StackIter>;
+    corsika::stack::nuclear_extension::ParticleDataStack::MPIType,
+    SetupGeometryDataInterface, StackIter>;
+
 using TestTrackingLineStack = corsika::stack::CombinedStack<
-    typename corsika::setup::detail::ParticleDataStack::StackImpl,
-    GeometryData<TestEnvironmentType>, StackWithGeometryInterface>;
+    typename corsika::stack::nuclear_extension::ParticleDataStack::StackImpl,
+    corsika::stack::node::GeometryData<TestEnvironmentType>, StackWithGeometryInterface>;
diff --git a/Processes/UrQMD/UrQMD.cc b/Processes/UrQMD/UrQMD.cc
index cc9e02f61d2f7c45144260e6871a17e63aa42c60..26814293241bfb877794687e7b6cf27790c947bb 100644
--- a/Processes/UrQMD/UrQMD.cc
+++ b/Processes/UrQMD/UrQMD.cc
@@ -8,6 +8,7 @@
 
 #include <corsika/geometry/QuantityVector.h>
 #include <corsika/geometry/Vector.h>
+#include <corsika/logging/Logging.h>
 #include <corsika/particles/ParticleProperties.h>
 #include <corsika/process/urqmd/UrQMD.h>
 #include <corsika/units/PhysicalUnits.h>
@@ -27,7 +28,7 @@ using namespace corsika::units::si;
 
 using SetupStack = corsika::setup::Stack;
 using SetupParticle = corsika::setup::Stack::StackIterator;
-using SetupProjectile = corsika::setup::StackView::StackIterator;
+using SetupView = corsika::setup::StackView;
 
 UrQMD::UrQMD(std::string const& xs_file) {
   readXSFile(xs_file);
@@ -39,6 +40,10 @@ CrossSectionType UrQMD::GetTabulatedCrossSection(particles::Code projectileCode,
                                                  HEPEnergyType labEnergy) const {
   // translated to C++ from CORSIKA 7 subroutine cxtot_u
 
+  C8LOG_DEBUG("UrQMD::GetTabulatedCrossSection proj={}, targ={}, E={}GeV",
+              particles::GetName(projectileCode), particles::GetName(targetCode),
+              labEnergy / 1_GeV);
+
   auto const kinEnergy = labEnergy - particles::GetMass(projectileCode);
 
   assert(kinEnergy >= HEPEnergyType::zero());
@@ -126,8 +131,8 @@ CrossSectionType UrQMD::GetCrossSection(particles::Code projectileCode,
       !IsNucleus(targetCode)) { // both particles are "special"
     auto const mProj = particles::GetMass(projectileCode);
     auto const mTar = particles::GetMass(targetCode);
-    double sqrtS = sqrt(units::si::detail::static_pow<2>(mProj) +
-                        units::si::detail::static_pow<2>(mTar) + 2 * labEnergy * mTar) *
+    double sqrtS = sqrt(units::static_pow<2>(mProj) + units::static_pow<2>(mTar) +
+                        2 * labEnergy * mTar) *
                    (1 / 1_GeV);
 
     // we must set some UrQMD globals first...
@@ -179,7 +184,7 @@ CrossSectionType UrQMD::GetCrossSection(particles::Code projectileCode,
     int const At = IsNucleus(targetCode) ? particles::GetNucleusA(targetCode) : 1;
 
     double const maxImpact = nucrad_(Ap) + nucrad_(At) + 2 * options_.CTParam[30 - 1];
-    return 10_mb * M_PI * units::si::detail::static_pow<2>(maxImpact);
+    return 10_mb * M_PI * units::static_pow<2>(maxImpact);
     // is a constant cross-section really reasonable?
   }
 }
@@ -240,9 +245,11 @@ GrammageType UrQMD::GetInteractionLength(SetupParticle const& particle) const {
          weightedProdCrossSection;
 }
 
-corsika::process::EProcessReturn UrQMD::DoInteraction(SetupProjectile& projectile) {
+corsika::process::EProcessReturn UrQMD::DoInteraction(SetupView& view) {
   using namespace units::si;
 
+  auto const projectile = view.GetProjectile();
+
   auto projectileCode = projectile.GetPID();
   auto const projectileEnergyLab = projectile.GetEnergy();
   auto const& projectileMomentumLab = projectile.GetMomentum();
@@ -343,7 +350,7 @@ corsika::process::EProcessReturn UrQMD::DoInteraction(SetupProjectile& projectil
     momentum.rebase(originalCS); // transform back into standard lab frame
     std::cout << i << " " << code << " " << momentum.GetComponents() << std::endl;
 
-    projectile.AddSecondary(
+    view.AddSecondary(
         std::tuple<particles::Code, HEPEnergyType, stack::MomentumVector, geometry::Point,
                    TimeType>{code, energy, momentum, projectilePosition, projectileTime});
   }
diff --git a/Processes/UrQMD/UrQMD.h b/Processes/UrQMD/UrQMD.h
index 3461c8ca36cd082eb0afc4731cdb44177c883201..937b6d702f63443fe0e221060e86739010ffcae8 100644
--- a/Processes/UrQMD/UrQMD.h
+++ b/Processes/UrQMD/UrQMD.h
@@ -40,8 +40,7 @@ namespace corsika::process::UrQMD {
     corsika::units::si::CrossSectionType GetTabulatedCrossSection(
         particles::Code, particles::Code, corsika::units::si::HEPEnergyType) const;
 
-    corsika::process::EProcessReturn DoInteraction(
-        corsika::setup::StackView::StackIterator&);
+    corsika::process::EProcessReturn DoInteraction(corsika::setup::StackView&);
 
     bool CanInteract(particles::Code) const;
 
diff --git a/Processes/UrQMD/testUrQMD.cc b/Processes/UrQMD/testUrQMD.cc
index fd947e83f60776d36fe986f0471ec827603e116b..2b1f5bcefeb69a5ea327dd3684c228b0c188a184 100644
--- a/Processes/UrQMD/testUrQMD.cc
+++ b/Processes/UrQMD/testUrQMD.cc
@@ -85,8 +85,7 @@ auto setupStack(int vA, int vZ, HEPEnergyType vMomentum, TNodeType* vNodePtr,
   geometry::Point const origin(cs, {0_m, 0_m, 0_m});
   corsika::stack::MomentumVector const pLab(cs, {vMomentum, 0_GeV, 0_GeV});
 
-  HEPEnergyType const E0 =
-      sqrt(units::si::detail::static_pow<2>(mN * vA) + pLab.squaredNorm());
+  HEPEnergyType const E0 = sqrt(units::static_pow<2>(mN * vA) + pLab.squaredNorm());
   auto particle =
       stack->AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
                                     corsika::stack::MomentumVector, geometry::Point,
@@ -95,8 +94,7 @@ auto setupStack(int vA, int vZ, HEPEnergyType vMomentum, TNodeType* vNodePtr,
 
   particle.SetNode(vNodePtr);
   return std::make_tuple(
-      std::move(stack),
-      std::make_unique<decltype(corsika::stack::SecondaryView(particle))>(particle));
+      std::move(stack), std::make_unique<decltype(setup::StackView{particle})>(particle));
 }
 
 template <typename TNodeType>
@@ -107,9 +105,8 @@ auto setupStack(particles::Code vProjectileType, HEPEnergyType vMomentum,
   geometry::Point const origin(cs, {0_m, 0_m, 0_m});
   corsika::stack::MomentumVector const pLab(cs, {vMomentum, 0_GeV, 0_GeV});
 
-  HEPEnergyType const E0 =
-      sqrt(units::si::detail::static_pow<2>(particles::GetMass(vProjectileType)) +
-           pLab.squaredNorm());
+  HEPEnergyType const E0 = sqrt(
+      units::static_pow<2>(particles::GetMass(vProjectileType)) + pLab.squaredNorm());
   auto particle = stack->AddParticle(
       std::tuple<particles::Code, units::si::HEPEnergyType,
                  corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
@@ -117,8 +114,7 @@ auto setupStack(particles::Code vProjectileType, HEPEnergyType vMomentum,
 
   particle.SetNode(vNodePtr);
   return std::make_tuple(
-      std::move(stack),
-      std::make_unique<decltype(corsika::stack::SecondaryView(particle))>(particle));
+      std::move(stack), std::make_unique<decltype(setup::StackView{particle})>(particle));
 }
 
 TEST_CASE("UrQMD") {
@@ -146,8 +142,8 @@ TEST_CASE("UrQMD") {
 
     for (auto code : validProjectileCodes) {
       auto [stack, view] = setupStack(code, 100_GeV, nodePtr, cs);
-      REQUIRE(stack->GetSize() == 1);
-      REQUIRE(view->GetSize() == 0);
+      REQUIRE(stack->getEntries() == 1);
+      REQUIRE(view->getEntries() == 0);
 
       // simple check whether the cross-section is non-vanishing
       // only nuclei with available tabluated data so far
@@ -162,13 +158,13 @@ TEST_CASE("UrQMD") {
 
     unsigned short constexpr A = 14, Z = 7;
     auto [stackPtr, secViewPtr] = setupStack(A, Z, 400_GeV, nodePtr, *csPtr);
-    REQUIRE(stackPtr->GetSize() == 1);
-    REQUIRE(secViewPtr->GetSize() == 0);
+    REQUIRE(stackPtr->getEntries() == 1);
+    REQUIRE(secViewPtr->getEntries() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->GetProjectile();
     auto const projectileMomentum = projectile.GetMomentum();
-    [[maybe_unused]] process::EProcessReturn const ret = urqmd.DoInteraction(projectile);
+    [[maybe_unused]] process::EProcessReturn const ret = urqmd.DoInteraction(*secViewPtr);
 
     REQUIRE(sumCharge(*secViewPtr) ==
             Z + particles::GetChargeNumber(particles::Code::Oxygen));
@@ -186,14 +182,14 @@ TEST_CASE("UrQMD") {
 
     auto [stackPtr, secViewPtr] =
         setupStack(particles::Code::PiPlus, 400_GeV, nodePtr, *csPtr);
-    REQUIRE(stackPtr->GetSize() == 1);
-    REQUIRE(secViewPtr->GetSize() == 0);
+    REQUIRE(stackPtr->getEntries() == 1);
+    REQUIRE(secViewPtr->getEntries() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->GetProjectile();
     auto const projectileMomentum = projectile.GetMomentum();
 
-    [[maybe_unused]] process::EProcessReturn const ret = urqmd.DoInteraction(projectile);
+    [[maybe_unused]] process::EProcessReturn const ret = urqmd.DoInteraction(*secViewPtr);
 
     REQUIRE(sumCharge(*secViewPtr) ==
             particles::GetChargeNumber(particles::Code::PiPlus) +
@@ -212,14 +208,14 @@ TEST_CASE("UrQMD") {
 
     auto [stackPtr, secViewPtr] =
         setupStack(particles::Code::K0Long, 400_GeV, nodePtr, *csPtr);
-    REQUIRE(stackPtr->GetSize() == 1);
-    REQUIRE(secViewPtr->GetSize() == 0);
+    REQUIRE(stackPtr->getEntries() == 1);
+    REQUIRE(secViewPtr->getEntries() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->GetProjectile();
     auto const projectileMomentum = projectile.GetMomentum();
 
-    [[maybe_unused]] process::EProcessReturn const ret = urqmd.DoInteraction(projectile);
+    [[maybe_unused]] process::EProcessReturn const ret = urqmd.DoInteraction(*secViewPtr);
 
     REQUIRE(sumCharge(*secViewPtr) ==
             particles::GetChargeNumber(particles::Code::K0Long) +
diff --git a/Setup/CMakeLists.txt b/Setup/CMakeLists.txt
index 62fed414210d221775b238ef0d70f1a949501029..21d59657638b3fa7974214f454632ca0417c7f97 100644
--- a/Setup/CMakeLists.txt
+++ b/Setup/CMakeLists.txt
@@ -3,6 +3,7 @@ set (
   SetupStack.h
   SetupEnvironment.h
   SetupTrajectory.h
+  GeometryNodeStackExtension.h
   )
 
 set (
@@ -17,10 +18,15 @@ target_link_libraries (
   CORSIKAsetup
   INTERFACE
   CORSIKAgeometry
-  SuperStupidStack
   NuclearStackExtension
+  GeometryNodeStackExtension
+  CORSIKAhistory
   )
 
+if (WITH_HISTORY)
+  target_compile_definitions (CORSIKAsetup INTERFACE "WITH_HISTORY")
+endif (WITH_HISTORY)
+
 target_include_directories (
   CORSIKAsetup
   INTERFACE 
diff --git a/Setup/SetupStack.h b/Setup/SetupStack.h
index 5bf8a2b49dee733f3e1d101fc8b60cbc4134de97..8930cb74eeae2ba63bbead4f79085617880ed0ba 100644
--- a/Setup/SetupStack.h
+++ b/Setup/SetupStack.h
@@ -8,142 +8,126 @@
 
 #pragma once
 
-// the basic particle data stack:
-#include <corsika/stack/super_stupid/SuperStupidStack.h>
-
-// extension with nuclear data for Code::Nucleus
+#include <corsika/stack/CombinedStack.h>
+#include <corsika/stack/node/GeometryNodeStackExtension.h>
 #include <corsika/stack/nuclear_extension/NuclearStackExtension.h>
+#include <corsika/stack/history/HistorySecondaryProducer.hpp>
+#include <corsika/stack/history/HistoryStackExtension.hpp>
 
-// extension with geometry information for tracking
-#include <corsika/environment/Environment.h>
 #include <corsika/setup/SetupEnvironment.h>
-#include <corsika/stack/CombinedStack.h>
 
-#include <tuple>
-#include <utility>
-#include <vector>
+namespace corsika::setup {
 
-// definition of stack-data object to store geometry information
-template <typename TEnvType>
+  namespace detail {
 
-/**
- * @class GeometryData
- *
- * definition of stack-data object to store geometry information
- */
-class GeometryData {
-
-public:
-  using BaseNodeType = typename TEnvType::BaseNodeType;
-
-  // these functions are needed for the Stack interface
-  void Clear() { fNode.clear(); }
-  unsigned int GetSize() const { return fNode.size(); }
-  unsigned int GetCapacity() const { return fNode.size(); }
-  void Copy(const int i1, const int i2) { fNode[i2] = fNode[i1]; }
-  void Swap(const int i1, const int i2) { std::swap(fNode[i1], fNode[i2]); }
-
-  // custom data access function
-  void SetNode(const int i, BaseNodeType const* v) { fNode[i] = v; }
-  auto const* GetNode(const int i) const { return fNode[i]; }
-
-  // these functions are also needed by the Stack interface
-  void IncrementSize() { fNode.push_back(nullptr); }
-  void DecrementSize() {
-    if (fNode.size() > 0) { fNode.pop_back(); }
-  }
-
-  // custom private data section
-private:
-  std::vector<const BaseNodeType*> fNode;
-};
-
-/**
- * @class GeometryDataInterface
- *
- * corresponding defintion of a stack-readout object, the iteractor
- * dereference operator will deliver access to these function
-// defintion of a stack-readout object, the iteractor dereference
-// operator will deliver access to these function
- */
-template <typename T, typename TEnvType>
-class GeometryDataInterface : public T {
-
-public:
-  using T::GetIndex;
-  using T::GetStackData;
-  using T::SetParticleData;
-  using BaseNodeType = typename TEnvType::BaseNodeType;
-
-  // default version for particle-creation from input data
-  void SetParticleData(const std::tuple<BaseNodeType const*> v) {
-    SetNode(std::get<0>(v));
-  }
-  void SetParticleData(GeometryDataInterface& parent,
-                       const std::tuple<BaseNodeType const*>) {
-    SetNode(parent.GetNode()); // copy Node from parent particle!
-  }
-  void SetParticleData() { SetNode(nullptr); }
-  void SetParticleData(GeometryDataInterface& parent) {
-    SetNode(parent.GetNode()); // copy Node from parent particle!
-  }
-  void SetNode(BaseNodeType const* v) { GetStackData().SetNode(GetIndex(), v); }
-  auto const* GetNode() const { return GetStackData().GetNode(GetIndex()); }
-};
+    // ------------------------------------------
+    // add geometry node tracking data to stack:
 
-namespace corsika::setup {
+    // the GeometryNode stack needs to know the type of geometry-nodes from the
+    // environment:
+    template <typename TStackIter>
+    using SetupGeometryDataInterface =
+        typename stack::node::MakeGeometryDataInterface<TStackIter,
+                                                        setup::SetupEnvironment>::type;
 
-  namespace detail {
+    // combine particle data stack with geometry information for tracking
+    template <typename TStackIter>
+    using StackWithGeometryInterface = corsika::stack::CombinedParticleInterface<
+        stack::nuclear_extension::ParticleDataStack::MPIType, SetupGeometryDataInterface,
+        TStackIter>;
 
-    //
-    // this is an auxiliary help typedef, which I don't know how to put
-    // into NuclearStackExtension.h where it belongs...
-    template <typename StackIter>
-    using ExtendedParticleInterfaceType =
-        corsika::stack::nuclear_extension::NuclearParticleInterface<
-            corsika::stack::super_stupid::SuperStupidStack::PIType, StackIter>;
-    //
+    using StackWithGeometry = corsika::stack::CombinedStack<
+        typename corsika::stack::nuclear_extension::ParticleDataStack::StackImpl,
+        corsika::stack::node::GeometryData<setup::SetupEnvironment>,
+        StackWithGeometryInterface>;
 
-    // the particle data stack with extra nuclear information:
-    using ParticleDataStack = corsika::stack::nuclear_extension::NuclearStackExtension<
-        corsika::stack::super_stupid::SuperStupidStack, ExtendedParticleInterfaceType>;
+    // ------------------------------------------
+    // Add [optional] history data to stack, too:
 
-    template <typename T>
-    using SetupGeometryDataInterface = GeometryDataInterface<T, setup::SetupEnvironment>;
+    // combine dummy stack with geometry information for tracking
+    template <typename TStackIter>
+    using StackWithHistoryInterface = corsika::stack::CombinedParticleInterface<
+        StackWithGeometry::MPIType, history::HistoryEventDataInterface, TStackIter>;
 
-    // combine particle data stack with geometry information for tracking
-    template <typename StackIter>
-    using StackWithGeometryInterface =
-        corsika::stack::CombinedParticleInterface<ParticleDataStack::PIType,
-                                                  SetupGeometryDataInterface, StackIter>;
+    using StackWithHistory =
+        corsika::stack::CombinedStack<typename StackWithGeometry::StackImpl,
+                                      history::HistoryEventData,
+                                      StackWithHistoryInterface>;
+
+  } // namespace detail
+
+  // ---------------------------------------
+  // this is the FINAL stack we use in C8:
 
-    using StackWithGeometry =
-        corsika::stack::CombinedStack<typename ParticleDataStack::StackImpl,
-                                      GeometryData<setup::SetupEnvironment>,
-                                      StackWithGeometryInterface>;
+#ifdef WITH_HISTORY
 
+  /*
+   * the version with history
+   */
+  using Stack = detail::StackWithHistory;
+  template <typename T1, template <typename> typename M2>
+  using StackViewProducer = corsika::history::HistorySecondaryProducer<T1, M2>;
+
+  namespace detail {
+    /*
+      See Issue 161
+
+      unfortunately clang does not support this in the same way (yet) as
+      gcc, so we have to distinguish here. If clang cataches up, we
+      could remove the clang branch here and also in
+      corsika::Cascade. The gcc code is much more generic and
+      universal. If we could do the gcc version, we won't had to define
+      StackView globally, we could do it with MakeView whereever it is
+      actually needed. Keep an eye on this!
+    */
+#if defined(__clang__)
+    using TheStackView = corsika::stack::SecondaryView<
+        typename corsika::setup::Stack::StackImpl,
+        // CHECK with CLANG: corsika::setup::Stack::MPIType>;
+        corsika::setup::detail::StackWithHistoryInterface, StackViewProducer>;
+#elif defined(__GNUC__) || defined(__GNUG__)
+    using TheStackView =
+        corsika::stack::MakeView<corsika::setup::Stack, StackViewProducer>::type;
+#endif
   } // namespace detail
 
-  // this is the REAL stack we use:
-  using Stack = detail::StackWithGeometry;
+#else // WITH_HISTORY
 
   /*
-    See Issue 161
-
-    unfortunately clang does not support this in the same way (yet) as
-    gcc, so we have to distinguish here. If clang cataches up, we
-    could remove the clang branch here and also in
-    corsika::Cascade. The gcc code is much more generic and
-    universal. If we could do the gcc version, we won't had to define
-    StackView globally, we could do it with MakeView whereever it is
-    actually needed. Keep an eye on this!
-  */
+   * the version without history
+   */
+  using Stack = detail::StackWithGeometry;
+  template <typename T1, template <typename> typename M2>
+  using StackViewProducer = corsika::stack::DefaultSecondaryProducer<T1, M2>;
+
+  namespace detail {
+    /*
+      See Issue 161
+
+      unfortunately clang does not support this in the same way (yet) as
+      gcc, so we have to distinguish here. If clang cataches up, we
+      could remove the clang branch here and also in
+      corsika::Cascade. The gcc code is much more generic and
+      universal. If we could do the gcc version, we won't had to define
+      StackView globally, we could do it with MakeView whereever it is
+      actually needed. Keep an eye on this!
+    */
 #if defined(__clang__)
-  using StackView =
-      corsika::stack::SecondaryView<typename corsika::setup::Stack::StackImpl,
-                                    corsika::setup::detail::StackWithGeometryInterface>;
+    using TheStackView =
+        corsika::stack::SecondaryView<typename corsika::setup::Stack::StackImpl,
+                                      // CHECK with CLANG:
+                                      // corsika::setup::Stack::MPIType>;
+                                      corsika::setup::detail::StackWithGeometryInterface>;
 #elif defined(__GNUC__) || defined(__GNUG__)
-  using StackView = corsika::stack::MakeView<corsika::setup::Stack>::type;
+    using TheStackView = corsika::stack::MakeView<corsika::setup::Stack>::type;
 #endif
+  } // namespace detail
+
+#endif
+
+  // ---------------------------------------
+  // this is the FINAL stackitertor (particle type) we use in C8:
+
+  using StackView = detail::TheStackView;
 
 } // namespace corsika::setup
diff --git a/Stack/CMakeLists.txt b/Stack/CMakeLists.txt
index 01130bb116008bb9c642baa76891630d4dca96e5..ae5ee2acaabd768f8ac536e95a5706cdaae629cd 100644
--- a/Stack/CMakeLists.txt
+++ b/Stack/CMakeLists.txt
@@ -1,3 +1,5 @@
 add_subdirectory (DummyStack)
 add_subdirectory (SuperStupidStack)
 add_subdirectory (NuclearStackExtension)
+add_subdirectory (GeometryNodeStackExtension)
+add_subdirectory (History)
diff --git a/Stack/DummyStack/CMakeLists.txt b/Stack/DummyStack/CMakeLists.txt
index 1ac3bf52e24feeb0dfd8a946bb671100cc3713fa..b7e09a9e49706079719e48c7a043ca4fa40404a7 100644
--- a/Stack/DummyStack/CMakeLists.txt
+++ b/Stack/DummyStack/CMakeLists.txt
@@ -26,3 +26,12 @@ install (
   DESTINATION
   include/${DummyStack_NAMESPACE}
   )
+
+# ----------------
+# code unit testing
+ CORSIKA_ADD_TEST(testDummyStack)
+ target_link_libraries (
+   testDummyStack
+   DummyStack
+   CORSIKAtesting
+   )
diff --git a/Stack/DummyStack/DummyStack.h b/Stack/DummyStack/DummyStack.h
index b075916efcfdcf2fb38160b699c16b203670db0e..26270b51cc0f8b9202ccdcba6dc268e6b29a3198 100644
--- a/Stack/DummyStack/DummyStack.h
+++ b/Stack/DummyStack/DummyStack.h
@@ -8,58 +8,79 @@
 
 #pragma once
 
+#include <corsika/logging/Logging.h>
 #include <corsika/particles/ParticleProperties.h>
 #include <corsika/stack/Stack.h>
 #include <corsika/units/PhysicalUnits.h>
 
 #include <string>
-#include <vector>
+#include <tuple>
 
 namespace corsika::stack {
 
   namespace dummy {
 
     /**
-     * Example of a particle object on the stack.
+     * Example of a particle object on the stack, with NO DATA.
      */
 
-    template <typename _Stack>
-    class ParticleRead : public StackIteratorInfo<_Stack, ParticleRead<_Stack> > {
+    /**
+       however, conceptually we need to provide fake data. A stack without data does not
+       work...
+     */
+
+    struct NoData { /* nothing */
+      int nothing = 0;
+    };
+
+    template <typename StackIteratorInterface>
+    class ParticleInterface
+        : public corsika::stack::ParticleBase<StackIteratorInterface> {
 
-      using StackIteratorInfo<_Stack, ParticleRead>::GetIndex;
-      using StackIteratorInfo<_Stack, ParticleRead>::GetStack;
+    protected:
+      using corsika::stack::ParticleBase<StackIteratorInterface>::GetStack;
+      using corsika::stack::ParticleBase<StackIteratorInterface>::GetStackData;
 
     public:
+      using corsika::stack::ParticleBase<StackIteratorInterface>::GetIndex;
+
+    public:
+      void SetParticleData(const std::tuple<NoData>& /*v*/) {}
+      void SetParticleData(ParticleInterface<StackIteratorInterface>& /*parent*/,
+                           const std::tuple<NoData>& /*v*/) {}
+
+      std::string as_string() const { return "dummy-data"; }
     };
 
     /**
      *
-     * Memory implementation of the most simple (stupid) particle stack object.
+     * Memory implementation of the most simple (no-data) particle stack object.
      */
 
     class DummyStackImpl {
 
     public:
-      void Init() {}
+      void Init() { entries_ = 0; }
 
-      void Clear() {}
+      void Clear() { entries_ = 0; }
 
-      int GetSize() const { return 0; }
-      int GetCapacity() const { return 0; }
+      int GetSize() const { return entries_; }
+      int GetCapacity() const { return entries_; }
 
       /**
        *   Function to copy particle at location i2 in stack to i1
        */
-      void Copy(const int i1, const int i2) {}
+      void Copy(const int /*i1*/, const int /*i2*/) {}
 
-    protected:
-      void IncrementSize() {}
-      void DecrementSize() {}
+      void IncrementSize() { entries_++; }
+      void DecrementSize() { entries_--; }
+
+    private:
+      int entries_ = 0;
 
     }; // end class DummyStackImpl
 
-    typedef StackIterator<DummyStackImpl, ParticleRead<DummyStackImpl> > Particle;
-    typedef Stack<DummyStackImpl, Particle> DummyStack;
+    typedef Stack<DummyStackImpl, ParticleInterface> DummyStack;
 
   } // namespace dummy
 
diff --git a/Stack/DummyStack/testDummyStack.cc b/Stack/DummyStack/testDummyStack.cc
new file mode 100644
index 0000000000000000000000000000000000000000..41a5731589d0909405f11de46188784eb9fa710b
--- /dev/null
+++ b/Stack/DummyStack/testDummyStack.cc
@@ -0,0 +1,41 @@
+/*
+ * (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 <corsika/stack/dummy/DummyStack.h>
+
+using namespace corsika;
+using namespace corsika::stack;
+
+#include <catch2/catch.hpp>
+
+#include <tuple>
+
+TEST_CASE("DummyStack", "[stack]") {
+
+  using TestStack = dummy::DummyStack;
+
+  dummy::NoData noData;
+
+  SECTION("write node") {
+
+    TestStack s;
+    s.AddParticle(std::tuple<dummy::NoData>{noData});
+    CHECK(s.getEntries() == 1);
+  }
+
+  SECTION("stack fill and cleanup") {
+
+    TestStack s;
+    // add 99 particles, each 10th particle is a nucleus with A=i and Z=A/2!
+    for (int i = 0; i < 99; ++i) { s.AddParticle(std::tuple<dummy::NoData>{noData}); }
+
+    CHECK(s.getEntries() == 99);
+    for (int i = 0; i < 99; ++i) s.GetNextParticle().Delete();
+    CHECK(s.getEntries() == 0);
+  }
+}
diff --git a/Stack/GeometryNodeStackExtension/CMakeLists.txt b/Stack/GeometryNodeStackExtension/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8d7a3c7d06606e871892cf99b9bc37a7b9a8bbad
--- /dev/null
+++ b/Stack/GeometryNodeStackExtension/CMakeLists.txt
@@ -0,0 +1,38 @@
+set (GeometryNodeStackExtension_HEADERS GeometryNodeStackExtension.h)
+set (GeometryNodeStackExtension_NAMESPACE corsika/stack/node)
+
+add_library (GeometryNodeStackExtension INTERFACE)
+
+CORSIKA_COPY_HEADERS_TO_NAMESPACE (GeometryNodeStackExtension ${GeometryNodeStackExtension_NAMESPACE} ${GeometryNodeStackExtension_HEADERS})
+
+target_link_libraries (
+  GeometryNodeStackExtension
+  INTERFACE
+  CORSIKAstackinterface
+  CORSIKAunits
+  CORSIKAparticles
+  CORSIKAgeometry
+  )
+
+target_include_directories (
+  GeometryNodeStackExtension
+  INTERFACE
+  $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
+  $<INSTALL_INTERFACE:include>
+  )
+
+install (
+  FILES
+  ${GeometryNodeStackExtension_HEADERS}
+  DESTINATION
+  include/${GeometryNodeStackExtension_NAMESPACE}
+  )
+
+# ----------------
+# code unit testing
+ CORSIKA_ADD_TEST(testGeometryNodeStackExtension)
+ target_link_libraries (
+   testGeometryNodeStackExtension
+   GeometryNodeStackExtension
+   CORSIKAtesting
+   )
diff --git a/Stack/GeometryNodeStackExtension/testGeometryNodeStackExtension.cc b/Stack/GeometryNodeStackExtension/testGeometryNodeStackExtension.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b566c2c8ebed2848b243a28bd0b21b28219fcfc1
--- /dev/null
+++ b/Stack/GeometryNodeStackExtension/testGeometryNodeStackExtension.cc
@@ -0,0 +1,90 @@
+/*
+ * (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 <corsika/stack/CombinedStack.h>
+#include <corsika/stack/dummy/DummyStack.h>
+#include <corsika/stack/node/GeometryNodeStackExtension.h>
+
+using namespace corsika;
+using namespace corsika::stack;
+
+#include <catch2/catch.hpp>
+
+#include <iostream>
+using namespace std;
+
+// this is our dummy environment, it only knows its trivial BaseNodeType
+class DummyEnv {
+public:
+  typedef int BaseNodeType;
+};
+
+// the GeometryNode stack needs to know the type of geometry-nodes from the DummyEnv:
+template <typename TStackIter>
+using DummyGeometryDataInterface =
+    typename corsika::stack::node::MakeGeometryDataInterface<TStackIter, DummyEnv>::type;
+
+// combine dummy stack with geometry information for tracking
+template <typename TStackIter>
+using StackWithGeometryInterface =
+    corsika::stack::CombinedParticleInterface<dummy::DummyStack::MPIType,
+                                              DummyGeometryDataInterface, TStackIter>;
+
+using TestStack =
+    corsika::stack::CombinedStack<typename stack::dummy::DummyStack::StackImpl,
+                                  stack::node::GeometryData<DummyEnv>,
+                                  StackWithGeometryInterface>;
+
+TEST_CASE("GeometryNodeStackExtension", "[stack]") {
+
+  dummy::NoData noData;
+
+  SECTION("write node") {
+
+    const int data = 5;
+
+    TestStack s;
+    s.AddParticle(std::make_tuple(noData), std::tuple<const int*>{&data});
+
+    CHECK(s.getEntries() == 1);
+  }
+
+  SECTION("write/read node") {
+    const int data = 15;
+
+    TestStack s;
+    auto p = s.AddParticle(std::make_tuple(noData));
+    p.SetNode(&data);
+    CHECK(s.getEntries() == 1);
+
+    const auto pout = s.GetNextParticle();
+    CHECK(*(pout.GetNode()) == 15);
+  }
+
+  SECTION("stack fill and cleanup") {
+
+    const int data = 16;
+
+    TestStack s;
+    // add 99 particles, each 10th particle is a nucleus with A=i and Z=A/2!
+    for (int i = 0; i < 99; ++i) {
+      auto p = s.AddParticle(std::tuple<dummy::NoData>{noData});
+      p.SetNode(&data);
+    }
+
+    CHECK(s.getEntries() == 99);
+    double v = 0;
+    for (int i = 0; i < 99; ++i) {
+      auto p = s.GetNextParticle();
+      v += *(p.GetNode());
+      p.Delete();
+    }
+    CHECK(v == 99 * data);
+    CHECK(s.getEntries() == 0);
+  }
+}
diff --git a/Stack/History/CMakeLists.txt b/Stack/History/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2a47b86189bef946269e8f2815a51b2cfcf171f6
--- /dev/null
+++ b/Stack/History/CMakeLists.txt
@@ -0,0 +1,69 @@
+set (
+  HISTORY_HEADERS
+  EventType.hpp
+  Event.hpp
+  HistorySecondaryProducer.hpp
+  HistoryObservationPlane.hpp
+  HistoryStackExtension.hpp
+  SecondaryParticle.hpp
+  )
+
+set (
+  HISTORY_NAMESPACE
+  corsika/stack/history
+  )
+
+if (WITH_HISTORY)
+  set (
+    HISTORY_SOURCES
+    HistoryObservationPlane.cpp
+    )
+  add_library (CORSIKAhistory STATIC ${HISTORY_SOURCES})
+  set (IS_INTERFACE "")
+else (WITH_HISTORY)
+  add_library (CORSIKAhistory INTERFACE)
+  set (IS_INTERFACE "INTERFACE")
+endif (WITH_HISTORY)
+
+CORSIKA_COPY_HEADERS_TO_NAMESPACE (CORSIKAhistory ${HISTORY_NAMESPACE} ${HISTORY_HEADERS})
+
+target_link_libraries (
+  CORSIKAhistory
+  ${IS_INTERFACE}
+  CORSIKAparticles
+  CORSIKAgeometry
+  CORSIKAprocesssequence # for HistoryObservationPlane
+  CORSIKAsetup # for HistoryObservationPlane
+  CORSIKAlogging
+  SuperStupidStack
+  NuclearStackExtension # for testHistoryView.cc
+  C8::ext::boost # for HistoryObservationPlane
+  )
+
+target_include_directories (
+  CORSIKAhistory
+  INTERFACE 
+  $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
+  $<INSTALL_INTERFACE:include/include>
+  )
+
+install (
+  FILES ${HISTORY_HEADERS} 
+  DESTINATION include/${HISTORY_NAMESPACE}
+  )
+
+# ----------------
+# code unit testing
+CORSIKA_ADD_TEST(testHistoryStack)
+target_link_libraries (
+  testHistoryStack
+  CORSIKAhistory
+  CORSIKAtesting
+  DummyStack
+  )
+CORSIKA_ADD_TEST(testHistoryView)
+target_link_libraries (
+  testHistoryView
+  CORSIKAhistory
+  CORSIKAtesting
+  )
diff --git a/Stack/History/Event.hpp b/Stack/History/Event.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..901d9db8f7c327f1ff4dab9f931114a5a1dc7b69
--- /dev/null
+++ b/Stack/History/Event.hpp
@@ -0,0 +1,68 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/logging/Logging.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/stack/history/EventType.hpp>
+#include <corsika/stack/history/SecondaryParticle.hpp>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace corsika::history {
+
+  class Event;
+  using EventPtr = std::shared_ptr<history::Event>;
+
+  class Event {
+
+    size_t projectile_index_ = 0; //!< index of projectile on stack
+    std::vector<SecondaryParticle> secondaries_;
+    EventPtr parent_event_;
+
+    EventType type_ = EventType::Uninitialized;
+
+    std::optional<corsika::particles::Code> targetCode_;
+
+  public:
+    Event() = default;
+
+    void setParentEvent(EventPtr const& evt) { parent_event_ = evt; }
+
+    bool hasParentEvent() const { return bool(parent_event_); }
+    EventPtr& parentEvent() { return parent_event_; }
+    EventPtr const& parentEvent() const { return parent_event_; }
+
+    void setProjectileIndex(size_t i) { projectile_index_ = i; }
+    size_t projectileIndex() const { return projectile_index_; }
+
+    size_t addSecondary(units::si::HEPEnergyType energy,
+                        geometry::Vector<units::si::hepmomentum_d> const& momentum,
+                        particles::Code pid) {
+      secondaries_.emplace_back(energy, momentum, pid);
+      return secondaries_.size() - 1;
+    }
+
+    std::vector<SecondaryParticle> const& secondaries() const { return secondaries_; }
+
+    void setTargetCode(const particles::Code t) { targetCode_ = t; }
+
+    std::string as_string() const {
+      return fmt::format("hasParent={}, projIndex={}, Nsec={}", hasParentEvent(),
+                         projectile_index_, secondaries_.size());
+    }
+
+    EventType eventType() const { return type_; }
+
+    void setEventType(EventType t) { type_ = t; }
+  };
+
+} // namespace corsika::history
diff --git a/Stack/History/EventType.hpp b/Stack/History/EventType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4781d5c64c085684a8ea139abda2aa856b56cd66
--- /dev/null
+++ b/Stack/History/EventType.hpp
@@ -0,0 +1,13 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+namespace corsika::history {
+  enum class EventType { Uninitialized, Interaction, Decay };
+}
diff --git a/Stack/History/HistoryObservationPlane.cc b/Stack/History/HistoryObservationPlane.cpp
similarity index 79%
rename from Stack/History/HistoryObservationPlane.cc
rename to Stack/History/HistoryObservationPlane.cpp
index d9374eee7e00ee8177cd3027b86a9fac7bc6b9cd..1109b0fcee5cc0c1f45e626adeeec7c134814257 100644
--- a/Stack/History/HistoryObservationPlane.cc
+++ b/Stack/History/HistoryObservationPlane.cpp
@@ -7,11 +7,11 @@
  */
 
 #include <corsika/logging/Logging.h>
-#include <corsika/history/HistoryObservationPlane.hpp>
+#include <corsika/stack/history/HistoryObservationPlane.hpp>
 
 #include <boost/histogram/ostream.hpp>
 
-#include <fstream>
+#include <iomanip>
 #include <iostream>
 
 using namespace corsika::units::si;
@@ -66,17 +66,20 @@ LengthType HistoryObservationPlane::MaxStepLength(setup::Stack::ParticleType con
 
 void HistoryObservationPlane::fillHistoryHistogram(
     setup::Stack::ParticleType const& muon) {
-  //  double const muonEnergy = muon.GetEnergy() / 1_eV;
+  double const muon_energy = muon.GetEnergy() / 1_GeV;
 
-  // auto parent = stack_.begin() + muon.GetEvent()->projectileIndex();
-  Event* event = muon.GetEvent().get();
-
-  int intCounter = 0;
+  int genctr{0};
+  Event const* event = muon.GetEvent().get();
   while (event) {
-    event = event->parentEvent().get();
-    intCounter++;
+    auto const projectile = stack_.cfirst() + event->projectileIndex();
+    if (event->eventType() == EventType::Interaction) {
+      genctr++;
+      double const projEnergy = projectile.GetEnergy() / 1_GeV;
+      int const pdg = static_cast<int>(particles::GetPDG(projectile.GetPID()));
+
+      histogram_(muon_energy, projEnergy, pdg);
+    }
+    event = event->parentEvent().get(); // projectile.GetEvent().get();
   }
-  histogram_(intCounter);
 }
 
-void HistoryObservationPlane::print() { std::cout << histogram_ << std::endl; }
diff --git a/Stack/History/HistoryObservationPlane.hpp b/Stack/History/HistoryObservationPlane.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fb67ca7f9b8897cb6d6f2b11c6ce02101a5ea653
--- /dev/null
+++ b/Stack/History/HistoryObservationPlane.hpp
@@ -0,0 +1,60 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/geometry/Plane.h>
+#include <corsika/process/ContinuousProcess.h>
+#include <corsika/setup/SetupStack.h>
+#include <corsika/setup/SetupTrajectory.h>
+#include <corsika/units/PhysicalUnits.h>
+
+#include <boost/histogram.hpp>
+
+#include <functional>
+
+namespace corsika::history {
+  namespace detail {
+    inline auto hist_factory() {
+      namespace bh = boost::histogram;
+      namespace bha = bh::axis;
+      auto h = bh::make_histogram(
+          bha::regular<float, bha::transform::log>{11 * 5, 1e0, 1e11, "muon energy"},
+          bha::regular<float, bha::transform::log>{11 * 5, 1e0, 1e11,
+                                                   "projectile energy"},
+          bha::category<int, bh::use_default, bha::option::growth_t>{
+              {211, -211, 2212, -2212}, "projectile PDG"});
+      return h;
+    }
+  } // namespace detail
+
+  class HistoryObservationPlane
+      : public corsika::process::ContinuousProcess<HistoryObservationPlane> {
+  public:
+    HistoryObservationPlane(setup::Stack const&, geometry::Plane const&, bool = true);
+
+    corsika::units::si::LengthType MaxStepLength(
+        corsika::setup::Stack::ParticleType const&,
+        corsika::setup::Trajectory const& vTrajectory);
+
+    corsika::process::EProcessReturn DoContinuous(
+        corsika::setup::Stack::ParticleType const& vParticle,
+        corsika::setup::Trajectory const& vTrajectory);
+
+    auto const& histogram() const { return histogram_; }
+
+  private:
+    void fillHistoryHistogram(setup::Stack::ParticleType const&);
+
+    setup::Stack const& stack_;
+    geometry::Plane const plane_;
+    bool const deleteOnHit_;
+
+    decltype(detail::hist_factory()) histogram_ = detail::hist_factory();
+  };
+} // namespace corsika::history
diff --git a/Stack/History/HistorySecondaryProducer.hpp b/Stack/History/HistorySecondaryProducer.hpp
index 2d3d560a32e9a242ee32488476f988e939c54d27..792a83d9538ee742c75b4474b1399edfd6139645 100644
--- a/Stack/History/HistorySecondaryProducer.hpp
+++ b/Stack/History/HistorySecondaryProducer.hpp
@@ -9,41 +9,30 @@
 #pragma once
 
 #include <corsika/stack/SecondaryView.h>
-#include <corsika/history/Event.hpp>
+#include <corsika/stack/history/Event.hpp>
 
 #include <corsika/logging/Logging.h>
 
+#include <boost/type_index.hpp>
+
 #include <memory>
 #include <type_traits>
 #include <utility>
 
 namespace corsika::history {
 
+  //! mix-in class for SecondaryView that fills secondaries into an \class Event
   template <class T1, template <class> class T2>
   class HistorySecondaryProducer {
-
-    using TView = corsika::stack::SecondaryView<T1, T2, HistorySecondaryProducer>;
-
   public:
     EventPtr event_;
+    static bool constexpr has_event{true};
 
   public:
-    HistorySecondaryProducer() :
-        event_{std::make_shared<Event>()} {
-      C8LOG_TRACE("HistorySecondaryProducer::HistorySecondaryProducer");
-    }
-
-    /**
-     * Method is called after a new SecondaryView has been
-     * created. Extra logic can be introduced here.  
-     * 
-     * The input Particle is a reference object into the original
-     * parent stack! It is not a reference into the SecondaryView
-     * itself.
-     */
     template <typename Particle>
-    void new_view(Particle& p) {
-      C8LOG_TRACE("HistorySecondaryProducer::new_view");
+    HistorySecondaryProducer(Particle const& p)
+        : event_{std::make_shared<Event>()} {
+      C8LOG_TRACE("HistorySecondaryProducer::HistorySecondaryProducer");
       event_->setProjectileIndex(p.GetIndex());
       event_->setParentEvent(p.GetEvent());
     }
@@ -51,7 +40,7 @@ namespace corsika::history {
     /**
      * Method is called after a new Secondary has been created on the
      * SecondaryView. Extra logic can be introduced here.
-     * 
+     *
      * The input Particle is the new secondary that was produced and
      * is of course a reference into the SecondaryView itself.
      */
@@ -60,12 +49,11 @@ namespace corsika::history {
       C8LOG_TRACE("HistorySecondaryProducer::new_secondary(sec)");
 
       // store particles at production time in Event here
-      auto const sec_index = event_->addSecondary(
-          sec.GetEnergy(), sec.GetMomentum(), sec.GetPID());
+      auto const sec_index =
+          event_->addSecondary(sec.GetEnergy(), sec.GetMomentum(), sec.GetPID());
       sec.SetParentEventIndex(sec_index);
       sec.SetEvent(event_);
     }
-
   };
 
 } // namespace corsika::history
diff --git a/Stack/History/HistoryStackExtension.hpp b/Stack/History/HistoryStackExtension.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..67a38b79617b8a5a5dca62fa6485ff17926f927a
--- /dev/null
+++ b/Stack/History/HistoryStackExtension.hpp
@@ -0,0 +1,139 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/logging/Logging.h>
+#include <corsika/stack/Stack.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace corsika::history {
+
+  /**
+   * @class HistoryData
+   *
+   * definition of stack-data object to store history information this
+   * is vector with shared_ptr<TEvent>, where TEvent is a free
+   * template parameter for customization.
+   */
+  template <typename TEvent>
+  class HistoryData {
+    using EventPtr =
+        std::shared_ptr<TEvent>; //!< Pointer to the event where this particle was created
+    using ParentEventIndex = int; //!< index to TEvent::secondaries_
+    using DataType = std::pair<EventPtr, ParentEventIndex>;
+
+  public:
+    // these functions are needed for the Stack interface
+    void Clear() { historyData_.clear(); }
+    unsigned int GetSize() const { return historyData_.size(); }
+    unsigned int GetCapacity() const { return historyData_.size(); }
+    void Copy(const int i1, const int i2) { historyData_[i2] = historyData_[i1]; }
+    void Swap(const int i1, const int i2) {
+      std::swap(historyData_[i1], historyData_[i2]);
+    }
+
+    // custom data access function
+    void SetEvent(const int i, EventPtr v) { historyData_[i].first = std::move(v); }
+    const EventPtr& GetEvent(const int i) const { return historyData_[i].first; }
+    EventPtr& GetEvent(const int i) { return historyData_[i].first; }
+
+    void SetParentEventIndex(const int i, ParentEventIndex v) {
+      historyData_[i].second = std::move(v);
+    }
+    ParentEventIndex GetParentEventIndex(const int i) const {
+      return historyData_[i].second;
+    }
+
+    // these functions are also needed by the Stack interface
+    void IncrementSize() { historyData_.push_back(DataType{}); }
+    void DecrementSize() {
+      if (historyData_.size() > 0) { historyData_.pop_back(); }
+    }
+
+    // custom private data section
+  private:
+    std::vector<DataType> historyData_;
+  };
+
+  /**
+   * @class HistoryDataInterface
+   *
+   * corresponding defintion of a stack-readout object, the iteractor
+   * dereference operator will deliver access to these function
+  // defintion of a stack-readout object, the iteractor dereference
+  // operator will deliver access to these function
+   */
+  template <typename T, typename TEvent>
+  class HistoryDataInterface : public T {
+  protected:
+    using T::GetStack;
+    using T::GetStackData;
+
+  public:
+    using T::GetIndex;
+
+  public:
+    // create a new particle from scratch
+    void SetParticleData() {
+      C8LOG_TRACE("HistoyDatatInterface::SetParticleData()");
+      GetStackData().SetParentEventIndex(GetIndex(), -1);
+    }
+
+    // create a new particle as secondary of a parent
+    void SetParticleData(HistoryDataInterface& /*parent*/) {
+      C8LOG_TRACE("HistoyDatatInterface::SetParticleData(parnt)");
+      SetParticleData();
+    }
+
+    void SetEvent(const std::shared_ptr<TEvent>& v) {
+      GetStackData().SetEvent(GetIndex(), v);
+    }
+
+    void SetParentEventIndex(int index) {
+      GetStackData().SetParentEventIndex(GetIndex(), index);
+    }
+
+    std::shared_ptr<TEvent> GetEvent() const {
+      return GetStackData().GetEvent(GetIndex());
+    }
+
+    int GetParentEventIndex() const {
+      return GetStackData().GetParentEventIndex(GetIndex());
+    }
+
+    std::string as_string() const {
+      return fmt::format("i_parent={}, [evt: {}]", GetParentEventIndex(),
+                         (bool(GetEvent()) ? GetEvent()->as_string() : "n/a"));
+    }
+  };
+
+  template <typename T, typename TEvent>
+  struct MakeHistoryDataInterface {
+    typedef HistoryDataInterface<T, TEvent> type;
+  };
+
+} // namespace corsika::history
+
+// for user-friendlyness we create the HistoryDataInterface type
+// with the histoy::Event data content right here:
+
+#include <corsika/stack/history/Event.hpp>
+
+namespace corsika::history {
+
+  template <typename TStackIter>
+  using HistoryEventDataInterface =
+      typename history::MakeHistoryDataInterface<TStackIter, history::Event>::type;
+
+  using HistoryEventData = history::HistoryData<history::Event>;
+
+} // namespace corsika::history
diff --git a/Stack/History/SecondaryParticle.hpp b/Stack/History/SecondaryParticle.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..249ea95bf97dec8313621e7d69a05ca4646db115
--- /dev/null
+++ b/Stack/History/SecondaryParticle.hpp
@@ -0,0 +1,37 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/geometry/Vector.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/units/PhysicalUnits.h>
+
+#include <vector>
+
+namespace corsika::history {
+
+  /**
+   * This class stores the non-common properties of secondaries in an event. All
+   * other (common) properties are available via the event itself or its projectile.
+   */
+  struct SecondaryParticle {
+    units::si::HEPEnergyType const energy_;
+    geometry::Vector<units::si::hepmomentum_d> const momentum_;
+    particles::Code const pid_;
+
+  public:
+    SecondaryParticle(units::si::HEPEnergyType energy,
+                      geometry::Vector<units::si::hepmomentum_d> momentum,
+                      particles::Code pid)
+        : energy_{energy}
+        , momentum_{momentum}
+        , pid_{pid} {}
+  };
+
+} // namespace corsika::history
diff --git a/Stack/History/testHistoryStack.cc b/Stack/History/testHistoryStack.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c068bdb6942ed57bcd705b8b001d8293267ce383
--- /dev/null
+++ b/Stack/History/testHistoryStack.cc
@@ -0,0 +1,66 @@
+/*
+ * (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 <corsika/stack/CombinedStack.h>
+#include <corsika/stack/dummy/DummyStack.h>
+#include <corsika/stack/history/HistoryStackExtension.hpp>
+
+#include <catch2/catch.hpp>
+
+using namespace corsika;
+using namespace corsika::stack;
+
+// this is our dummy environment, it only knows its trivial BaseNodeType
+class DummyEvent {
+private:
+  size_t parent_;
+  std::vector<int> secondaries_;
+
+public:
+  DummyEvent() {}
+  DummyEvent(const size_t parent) { parent_ = parent; }
+
+  size_t getParentIndex() { return parent_; }
+  void addSecondary(const int particle) { secondaries_.push_back(particle); }
+  int multiplicity() const { return secondaries_.size(); }
+};
+
+// the GeometryNode stack needs to know the type of geometry-nodes from the DummyEnv:
+template <typename TStackIter>
+using DummyHistoryDataInterface =
+    typename history::MakeHistoryDataInterface<TStackIter, DummyEvent>::type;
+
+// combine dummy stack with geometry information for tracking
+template <typename TStackIter>
+using StackWithHistoryInterface =
+    corsika::stack::CombinedParticleInterface<dummy::DummyStack::MPIType,
+                                              DummyHistoryDataInterface, TStackIter>;
+
+using TestStack =
+    corsika::stack::CombinedStack<typename stack::dummy::DummyStack::StackImpl,
+                                  history::HistoryData<DummyEvent>,
+                                  StackWithHistoryInterface>;
+
+using EvtPtr = std::shared_ptr<DummyEvent>;
+
+TEST_CASE("HistoryStackExtension", "[stack]") {
+
+  logging::SetLevel(logging::level::debug);
+
+  const dummy::NoData noData;
+  TestStack s;
+
+  auto p = s.AddParticle(std::tuple<dummy::NoData>{noData});
+
+  SECTION("add lone particle") {
+    CHECK(s.getEntries() == 1);
+
+    EvtPtr evt = p.GetEvent();
+    CHECK(evt == nullptr);
+  }
+}
diff --git a/Stack/History/testHistoryView.cc b/Stack/History/testHistoryView.cc
new file mode 100644
index 0000000000000000000000000000000000000000..588537198209b498af7358e27838fc3540c30590
--- /dev/null
+++ b/Stack/History/testHistoryView.cc
@@ -0,0 +1,221 @@
+/*
+ * (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 <corsika/stack/history/Event.hpp>
+#include <corsika/stack/history/HistorySecondaryProducer.hpp>
+#include <corsika/stack/history/HistoryStackExtension.hpp>
+
+#include <corsika/stack/CombinedStack.h>
+#include <corsika/stack/dummy/DummyStack.h>
+#include <corsika/stack/nuclear_extension/NuclearStackExtension.h>
+
+#include <corsika/logging/Logging.h>
+
+#include <catch2/catch.hpp>
+
+using namespace corsika;
+using namespace corsika::geometry;
+using namespace corsika::units::si;
+
+/**
+   Need to replicate setup::SetupStack in a maximally simplified
+   way, but with real particle data
+ */
+
+// combine dummy stack with geometry information for tracking
+template <typename TStackIter>
+using StackWithHistoryInterface = corsika::stack::CombinedParticleInterface<
+    stack::nuclear_extension::ParticleDataStack::MPIType,
+    history::HistoryEventDataInterface, TStackIter>;
+
+using TestStack = corsika::stack::CombinedStack<
+    typename stack::nuclear_extension::ParticleDataStack::StackImpl,
+    history::HistoryEventData, StackWithHistoryInterface>;
+
+/*
+    See Issue 161
+
+    unfortunately clang does not support this in the same way (yet) as
+    gcc, so we have to distinguish here. If clang cataches up, we
+    could remove the clang branch here and also in
+    corsika::Cascade. The gcc code is much more generic and
+    universal. If we could do the gcc version, we won't had to define
+    StackView globally, we could do it with MakeView whereever it is
+    actually needed. Keep an eye on this!
+  */
+#if defined(__clang__)
+using TheTestStackView = corsika::stack::SecondaryView<typename TestStack::StackImpl,
+                                                       StackWithHistoryInterface,
+                                                       history::HistorySecondaryProducer>;
+#elif defined(__GNUC__) || defined(__GNUG__)
+using TheTestStackView =
+    corsika::stack::MakeView<TestStack, history::HistorySecondaryProducer>::type;
+#endif
+
+using TestStackView = TheTestStackView;
+
+template <typename Event>
+int count_generations(Event const* event) {
+  int genCounter = 0;
+  while (event) {
+    event = event->parentEvent().get();
+    genCounter++;
+  }
+
+  return genCounter;
+}
+
+TEST_CASE("HistoryStackExtension", "[stack]") {
+
+  logging::SetLevel(logging::level::debug);
+
+  geometry::CoordinateSystem& dummyCS =
+      geometry::RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
+
+  // in this test we only use one singel stack !
+  TestStack stack;
+
+  // add primary particle
+  auto p0 = stack.AddParticle(
+      std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                      corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                      Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+
+  CHECK(stack.getEntries() == 1);
+  corsika::history::EventPtr evt = p0.GetEvent();
+  CHECK(evt == nullptr);
+  CHECK(count_generations(evt.get()) == 0);
+
+  SECTION("interface test, view") {
+
+    // add secondaries, 1st generation
+    TestStackView hview0(p0);
+
+    auto const ev0 = p0.GetEvent();
+    CHECK(ev0 == nullptr);
+
+    C8LOG_DEBUG("loop VIEW");
+
+    // add 5 secondaries
+    for (int i = 0; i < 5; ++i) {
+      auto sec = hview0.AddSecondary(
+          std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                          corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                          Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+
+      CHECK(sec.GetParentEventIndex() == i);
+      CHECK(sec.GetEvent() != nullptr);
+      CHECK(sec.GetEvent()->parentEvent() == nullptr);
+      CHECK(count_generations(sec.GetEvent().get()) == 1);
+    }
+
+    // read 1st genertion particle particle
+    auto p1 = stack.GetNextParticle();
+    CHECK(count_generations(p1.GetEvent().get()) == 1);
+
+    TestStackView hview1(p1);
+
+    auto const ev1 = p1.GetEvent();
+
+    // add second generation of secondaries
+    // add 10 secondaries
+    for (int i = 0; i < 10; ++i) {
+      auto sec = hview1.AddSecondary(
+          std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                          corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                          Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+
+      CHECK(sec.GetParentEventIndex() == i);
+      CHECK(sec.GetEvent()->parentEvent() == ev1);
+      CHECK(sec.GetEvent()->parentEvent()->parentEvent() == ev0);
+
+      CHECK(count_generations(sec.GetEvent().get()) == 2);
+
+      const auto org_projectile = stack.at(sec.GetEvent()->projectileIndex());
+      CHECK(org_projectile.GetEvent() == sec.GetEvent()->parentEvent());
+    }
+
+    // read 2nd genertion particle particle
+    auto p2 = stack.GetNextParticle();
+
+    TestStackView hview2(p2);
+
+    auto const ev2 = p2.GetEvent();
+
+    // add third generation of secondaries
+    // add 15 secondaries
+    for (int i = 0; i < 15; ++i) {
+      C8LOG_TRACE("loop, view: " + std::to_string(i));
+
+      auto sec = hview2.AddSecondary(
+          std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                          corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                          Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+      C8LOG_TRACE("loop, ---- ");
+
+      CHECK(sec.GetParentEventIndex() == i);
+      CHECK(sec.GetEvent()->parentEvent() == ev2);
+      CHECK(sec.GetEvent()->parentEvent()->parentEvent() == ev1);
+      CHECK(sec.GetEvent()->parentEvent()->parentEvent()->parentEvent() == ev0);
+
+      CHECK(count_generations(sec.GetEvent().get()) == 3);
+    }
+  }
+
+  SECTION("also test projectile access") {
+
+    C8LOG_TRACE("projectile test");
+
+    // add secondaries, 1st generation
+    TestStackView hview0(p0);
+    auto proj0 = hview0.GetProjectile();
+    auto const ev0 = p0.GetEvent();
+    CHECK(ev0 == nullptr);
+
+    C8LOG_TRACE("loop");
+
+    // add 5 secondaries
+    for (int i = 0; i < 5; ++i) {
+      C8LOG_TRACE("loop " + std::to_string(i));
+      auto sec = proj0.AddSecondary(
+          std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                          corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                          Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+
+      CHECK(sec.GetParentEventIndex() == i);
+      CHECK(sec.GetEvent() != nullptr);
+      CHECK(sec.GetEvent()->parentEvent() == nullptr);
+    }
+    CHECK(stack.getEntries() == 6);
+
+    // read 1st genertion particle particle
+    auto p1 = stack.GetNextParticle();
+
+    TestStackView hview1(p1);
+    auto proj1 = hview1.GetProjectile();
+    auto const ev1 = p1.GetEvent();
+
+    // add second generation of secondaries
+    // add 10 secondaries
+    for (int i = 0; i < 10; ++i) {
+      auto sec = proj1.AddSecondary(
+          std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                          corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                          Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+
+      CHECK(sec.GetParentEventIndex() == i);
+      CHECK(sec.GetEvent()->parentEvent() == ev1);
+      CHECK(sec.GetEvent()->secondaries().size() == i + 1);
+      CHECK(sec.GetEvent()->parentEvent()->parentEvent() == ev0);
+
+      const auto org_projectile = stack.at(sec.GetEvent()->projectileIndex());
+      CHECK(org_projectile.GetEvent() == sec.GetEvent()->parentEvent());
+    }
+    CHECK(stack.getEntries() == 16);
+  }
+}
diff --git a/Stack/NuclearStackExtension/CMakeLists.txt b/Stack/NuclearStackExtension/CMakeLists.txt
index c33487b15e1c53c3d2e7de7984cd8ed012afe8d2..3e3c5e735317dbc76f9003a9a3d928df8f40a720 100644
--- a/Stack/NuclearStackExtension/CMakeLists.txt
+++ b/Stack/NuclearStackExtension/CMakeLists.txt
@@ -12,6 +12,7 @@ target_link_libraries (
   CORSIKAunits
   CORSIKAparticles
   CORSIKAgeometry
+  SuperStupidStack
   )
 
 target_include_directories (
@@ -33,7 +34,6 @@ install (
 CORSIKA_ADD_TEST(testNuclearStackExtension)
 target_link_libraries (
   testNuclearStackExtension
-  SuperStupidStack
   NuclearStackExtension
   CORSIKAparticles
   CORSIKAgeometry
diff --git a/Stack/NuclearStackExtension/NuclearStackExtension.h b/Stack/NuclearStackExtension/NuclearStackExtension.h
index e1827f725c09b845ee062926fb70df7e1439e65c..4409a215eed0c331e922b311235f37e10ded24d8 100644
--- a/Stack/NuclearStackExtension/NuclearStackExtension.h
+++ b/Stack/NuclearStackExtension/NuclearStackExtension.h
@@ -9,12 +9,17 @@
 #pragma once
 
 #include <corsika/particles/ParticleProperties.h>
+
 #include <corsika/stack/Stack.h>
+#include <corsika/stack/super_stupid/SuperStupidStack.h>
+
 #include <corsika/units/PhysicalUnits.h>
 
 #include <corsika/geometry/Point.h>
 #include <corsika/geometry/Vector.h>
 
+#include <corsika/logging/Logging.h>
+
 #include <algorithm>
 #include <tuple>
 #include <vector>
@@ -24,7 +29,10 @@ namespace corsika::stack {
   /**
    * @namespace nuclear_extension
    *
-   * Add A and Z data to existing stack of particle properties.
+   * Add A and Z data to existing stack (currently SuperStupidStack) of particle
+   * properties. This is done via inheritance, not via CombinedStack since the nuclear
+   * data is stored ONLY when needed (for nuclei) and not for all particles. Thus, this is
+   * a new, derived Stack object.
    *
    * Only for Code::Nucleus particles A and Z are stored, not for all
    * normal elementary particles.
@@ -54,6 +62,7 @@ namespace corsika::stack {
     protected:
       using InnerParticleInterface<StackIteratorInterface>::GetStackData;
       using InnerParticleInterface<StackIteratorInterface>::GetIndex;
+      using InnerParticleInterface<StackIteratorInterface>::as_string;
 
     public:
       void SetParticleData(
@@ -135,6 +144,13 @@ namespace corsika::stack {
                                                         std::get<4>(v)});
       }
 
+      std::string as_string() const {
+        return fmt::format(
+            "{}, nuc({})", InnerParticleInterface<StackIteratorInterface>::as_string(),
+            (isNucleus() ? fmt::format("A={}, Z={}", GetNuclearA(), GetNuclearZ())
+                         : "n/a"));
+      }
+
       /**
        * @name individual setters
        * @{
@@ -178,6 +194,7 @@ namespace corsika::stack {
 
     protected:
       void SetNucleusRef(const int vR) { GetStackData().SetNucleusRef(GetIndex(), vR); }
+      bool isNucleus() const { return GetStackData().isNucleus(GetIndex()); }
     };
 
     /**
@@ -234,6 +251,8 @@ namespace corsika::stack {
         throw std::runtime_error(err.str());
       }
 
+      bool isNucleus(const unsigned int i) const { return fNucleusRef[i] >= 0; }
+
       /**
        *   Function to copy particle at location i1 in stack to i2
        */
@@ -324,48 +343,19 @@ namespace corsika::stack {
 
     }; // end class NuclearStackExtensionImpl
 
-    //    template<typename StackIteratorInterface>
-    // using NuclearParticleInterfaceType<StackIteratorInterface> =
-    // NuclearParticleInterface< ,StackIteratorInterface>
-
-    // works, but requires stupd _PI class
-    // template<typename SS> using TEST =
-    // NuclearParticleInterface<corsika::stack::super_stupid::SuperStupidStack::PIType,
-    // SS>;
     template <typename InnerStack, template <typename> typename _PI>
     using NuclearStackExtension =
         Stack<NuclearStackExtensionImpl<typename InnerStack::StackImpl>, _PI>;
 
-    // ----
-
-    // I'm dont't manage to do this properly.......
-    /*
-    template<typename TT, typename SS> using TESTi = typename
-    NuclearParticleInterface<TT::template PIType, SS>::ExtendedParticleInterface;
-    template<typename TT, typename SS> using TEST1 = TESTi<TT, SS>;
-    template<typename SS> using TEST2 = TEST1<typename
-    corsika::stack::super_stupid::SuperStupidStack, SS>;
-
-    using NuclearStackExtension = Stack<NuclearStackExtensionImpl<typename
-    InnerStack::StackImpl>, TEST2>;
-    */
-    /*
-      // .... this should be fixed ....
-
-    template <typename InnerStack, typename SS=StackIteratorInterface>
-      //using NuclearStackExtension = Stack<NuclearStackExtensionImpl<typename
-    InnerStack::StackImpl>, NuclearParticleInterface<typename InnerStack::template PIType,
-    StackIteratorInterface>::ExtendedParticleInterface>; using NuclearStackExtension =
-    Stack<NuclearStackExtensionImpl<typename InnerStack::StackImpl>, TEST1<typename
-    corsika::stack::super_stupid::SuperStupidStack, SS> >;
-
-    //template <typename InnerStack>
-      //  using NuclearStackExtension = Stack<NuclearStackExtensionImpl<typename
-    InnerStack::StackImpl>, TEST<typename
-    corsika::stack::super_stupid::SuperStupidStack::PIType>>;
-    //using NuclearStackExtension = Stack<NuclearStackExtensionImpl<typename
-    InnerStack::StackImpl>, TEST>;
-    */
+    //
+    template <typename StackIter>
+    using ExtendedParticleInterfaceType =
+        corsika::stack::nuclear_extension::NuclearParticleInterface<
+            corsika::stack::super_stupid::SuperStupidStack::MPIType, StackIter>;
+
+    // the particle data stack with extra nuclear information:
+    using ParticleDataStack = corsika::stack::nuclear_extension::NuclearStackExtension<
+        corsika::stack::super_stupid::SuperStupidStack, ExtendedParticleInterfaceType>;
 
   } // namespace nuclear_extension
 } // namespace corsika::stack
diff --git a/Stack/NuclearStackExtension/testNuclearStackExtension.cc b/Stack/NuclearStackExtension/testNuclearStackExtension.cc
index a6ae932e7f7a428e51499e8403715b01233933df..6896953a6079ba0c66b4ba630cf96a2c78a3bd48 100644
--- a/Stack/NuclearStackExtension/testNuclearStackExtension.cc
+++ b/Stack/NuclearStackExtension/testNuclearStackExtension.cc
@@ -8,7 +8,6 @@
 
 #include <corsika/geometry/RootCoordinateSystem.h>
 #include <corsika/stack/nuclear_extension/NuclearStackExtension.h>
-#include <corsika/stack/super_stupid/SuperStupidStack.h>
 #include <corsika/units/PhysicalUnits.h>
 
 using namespace corsika;
@@ -18,16 +17,6 @@ using namespace corsika::units::si;
 
 #include <catch2/catch.hpp>
 
-// this is an auxiliary help typedef, which I don't know how to put
-// into NuclearStackExtension.h where it belongs...
-template <typename StackIter>
-using ExtendedParticleInterfaceType =
-    corsika::stack::nuclear_extension::NuclearParticleInterface<
-        corsika::stack::super_stupid::SuperStupidStack::template PIType, StackIter>;
-
-using ExtStack = NuclearStackExtension<corsika::stack::super_stupid::SuperStupidStack,
-                                       ExtendedParticleInterfaceType>;
-
 #include <iostream>
 using namespace std;
 
@@ -41,127 +30,106 @@ TEST_CASE("NuclearStackExtension", "[stack]") {
                           ExtendedParticleInterfaceType>
         s;
     s.AddParticle(
-        std::tuple<particles::Code, units::si::HEPEnergyType,
-                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            particles::Code::Electron, 1.5_GeV,
-            corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
-    REQUIRE(s.GetSize() == 1);
+        std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
+    CHECK(s.getEntries() == 1);
   }
 
   SECTION("write nucleus") {
     NuclearStackExtension<corsika::stack::super_stupid::SuperStupidStack,
                           ExtendedParticleInterfaceType>
         s;
-    s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                             corsika::stack::MomentumVector, geometry::Point,
-                             units::si::TimeType, unsigned short, unsigned short>{
+    s.AddParticle(std::make_tuple(
         particles::Code::Nucleus, 1.5_GeV,
         corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, 10, 10});
-    REQUIRE(s.GetSize() == 1);
+        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, 10, 10));
+    CHECK(s.getEntries() == 1);
   }
 
   SECTION("write invalid nucleus") {
-    ExtStack s;
-    REQUIRE_THROWS(
-        s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                 corsika::stack::MomentumVector, geometry::Point,
-                                 units::si::TimeType, unsigned short, unsigned short>{
-            particles::Code::Nucleus, 1.5_GeV,
-            corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, 0, 0}));
+    ParticleDataStack s;
+    CHECK_THROWS(s.AddParticle(
+        std::make_tuple(particles::Code::Nucleus, 1.5_GeV,
+                        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, 0, 0)));
   }
 
   SECTION("read non nucleus") {
-    ExtStack s;
+    ParticleDataStack s;
     s.AddParticle(
-        std::tuple<particles::Code, units::si::HEPEnergyType,
-                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            particles::Code::Electron, 1.5_GeV,
-            corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
+        std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
     const auto pout = s.GetNextParticle();
-    REQUIRE(pout.GetPID() == particles::Code::Electron);
-    REQUIRE(pout.GetEnergy() == 1.5_GeV);
-    REQUIRE(pout.GetTime() == 100_s);
+    CHECK(pout.GetPID() == particles::Code::Electron);
+    CHECK(pout.GetEnergy() == 1.5_GeV);
+    CHECK(pout.GetTime() == 100_s);
   }
 
   SECTION("read nucleus") {
-    ExtStack s;
-    s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                             corsika::stack::MomentumVector, geometry::Point,
-                             units::si::TimeType, unsigned short, unsigned short>{
-        particles::Code::Nucleus, 1.5_GeV,
-        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, 10, 9});
+    ParticleDataStack s;
+    s.AddParticle(
+        std::make_tuple(particles::Code::Nucleus, 1.5_GeV,
+                        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, 10, 9));
     const auto pout = s.GetNextParticle();
-    REQUIRE(pout.GetPID() == particles::Code::Nucleus);
-    REQUIRE(pout.GetEnergy() == 1.5_GeV);
-    REQUIRE(pout.GetTime() == 100_s);
-    REQUIRE(pout.GetNuclearA() == 10);
-    REQUIRE(pout.GetNuclearZ() == 9);
+    CHECK(pout.GetPID() == particles::Code::Nucleus);
+    CHECK(pout.GetEnergy() == 1.5_GeV);
+    CHECK(pout.GetTime() == 100_s);
+    CHECK(pout.GetNuclearA() == 10);
+    CHECK(pout.GetNuclearZ() == 9);
   }
 
   SECTION("read invalid nucleus") {
-    ExtStack s;
+    ParticleDataStack s;
     s.AddParticle(
-        std::tuple<particles::Code, units::si::HEPEnergyType,
-                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            particles::Code::Electron, 1.5_GeV,
-            corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
+        std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
     const auto pout = s.GetNextParticle();
-    REQUIRE_THROWS(pout.GetNuclearA());
-    REQUIRE_THROWS(pout.GetNuclearZ());
+    CHECK_THROWS(pout.GetNuclearA());
+    CHECK_THROWS(pout.GetNuclearZ());
   }
 
   SECTION("stack fill and cleanup") {
 
-    ExtStack s;
+    ParticleDataStack s;
     // add 99 particles, each 10th particle is a nucleus with A=i and Z=A/2!
     for (int i = 0; i < 99; ++i) {
       if ((i + 1) % 10 == 0) {
-        s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                 corsika::stack::MomentumVector, geometry::Point,
-                                 units::si::TimeType, unsigned short, unsigned short>{
+        s.AddParticle(std::make_tuple(
             particles::Code::Nucleus, 1.5_GeV,
             corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, i, i / 2});
+            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, i, i / 2));
       } else {
-        s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                 corsika::stack::MomentumVector, geometry::Point,
-                                 units::si::TimeType>{
+        s.AddParticle(std::make_tuple(
             particles::Code::Electron, 1.5_GeV,
             corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
+            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
       }
     }
 
-    REQUIRE(s.GetSize() == 99);
+    CHECK(s.getEntries() == 99);
     for (int i = 0; i < 99; ++i) s.GetNextParticle().Delete();
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getEntries() == 0);
   }
 
   SECTION("stack operations") {
 
-    ExtStack s;
+    ParticleDataStack s;
     // add 99 particles, each 10th particle is a nucleus with A=i and Z=A/2!
     for (int i = 0; i < 99; ++i) {
       if ((i + 1) % 10 == 0) {
-        s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                 corsika::stack::MomentumVector, geometry::Point,
-                                 units::si::TimeType, unsigned short, unsigned short>{
+        s.AddParticle(std::make_tuple(
             particles::Code::Nucleus, i * 15_GeV,
             corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, i, i / 2});
+            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s, i, i / 2));
       } else {
-        s.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                 corsika::stack::MomentumVector, geometry::Point,
-                                 units::si::TimeType>{
+        s.AddParticle(std::make_tuple(
             particles::Code::Electron, i * 1.5_GeV,
             corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
+            Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
       }
     }
 
@@ -171,17 +139,17 @@ TEST_CASE("NuclearStackExtension", "[stack]") {
       const auto& p9 = s.cbegin() + 9;
       const auto& p10 = s.cbegin() + 10;
 
-      REQUIRE(p9.GetPID() == particles::Code::Nucleus);
-      REQUIRE(p9.GetEnergy() == 9 * 15_GeV);
-      REQUIRE(p9.GetTime() == 100_s);
-      REQUIRE(p9.GetNuclearA() == 9);
-      REQUIRE(p9.GetNuclearZ() == 9 / 2);
-
-      REQUIRE(p10.GetPID() == particles::Code::Nucleus);
-      REQUIRE(p10.GetEnergy() == 9 * 15_GeV);
-      REQUIRE(p10.GetTime() == 100_s);
-      REQUIRE(p10.GetNuclearA() == 9);
-      REQUIRE(p10.GetNuclearZ() == 9 / 2);
+      CHECK(p9.GetPID() == particles::Code::Nucleus);
+      CHECK(p9.GetEnergy() == 9 * 15_GeV);
+      CHECK(p9.GetTime() == 100_s);
+      CHECK(p9.GetNuclearA() == 9);
+      CHECK(p9.GetNuclearZ() == 9 / 2);
+
+      CHECK(p10.GetPID() == particles::Code::Nucleus);
+      CHECK(p10.GetEnergy() == 9 * 15_GeV);
+      CHECK(p10.GetTime() == 100_s);
+      CHECK(p10.GetNuclearA() == 9);
+      CHECK(p10.GetNuclearZ() == 9 / 2);
     }
 
     // copy
@@ -190,13 +158,13 @@ TEST_CASE("NuclearStackExtension", "[stack]") {
       const auto& p93 = s.cbegin() + 93;
       const auto& p9 = s.cbegin() + 9;
 
-      REQUIRE(p9.GetPID() == particles::Code::Electron);
-      REQUIRE(p9.GetEnergy() == 93 * 1.5_GeV);
-      REQUIRE(p9.GetTime() == 100_s);
+      CHECK(p9.GetPID() == particles::Code::Electron);
+      CHECK(p9.GetEnergy() == 93 * 1.5_GeV);
+      CHECK(p9.GetTime() == 100_s);
 
-      REQUIRE(p93.GetPID() == particles::Code::Electron);
-      REQUIRE(p93.GetEnergy() == 93 * 1.5_GeV);
-      REQUIRE(p93.GetTime() == 100_s);
+      CHECK(p93.GetPID() == particles::Code::Electron);
+      CHECK(p93.GetEnergy() == 93 * 1.5_GeV);
+      CHECK(p93.GetTime() == 100_s);
     }
 
     // swap
@@ -205,15 +173,15 @@ TEST_CASE("NuclearStackExtension", "[stack]") {
       const auto& p11 = s.cbegin() + 11; // now: nucleus
       const auto& p10 = s.cbegin() + 10; // now: electron
 
-      REQUIRE(p11.GetPID() == particles::Code::Nucleus);
-      REQUIRE(p11.GetEnergy() == 9 * 15_GeV);
-      REQUIRE(p11.GetTime() == 100_s);
-      REQUIRE(p11.GetNuclearA() == 9);
-      REQUIRE(p11.GetNuclearZ() == 9 / 2);
+      CHECK(p11.GetPID() == particles::Code::Nucleus);
+      CHECK(p11.GetEnergy() == 9 * 15_GeV);
+      CHECK(p11.GetTime() == 100_s);
+      CHECK(p11.GetNuclearA() == 9);
+      CHECK(p11.GetNuclearZ() == 9 / 2);
 
-      REQUIRE(p10.GetPID() == particles::Code::Electron);
-      REQUIRE(p10.GetEnergy() == 11 * 1.5_GeV);
-      REQUIRE(p10.GetTime() == 100_s);
+      CHECK(p10.GetPID() == particles::Code::Electron);
+      CHECK(p10.GetEnergy() == 11 * 1.5_GeV);
+      CHECK(p10.GetTime() == 100_s);
     }
 
     // swap two nuclei
@@ -222,20 +190,20 @@ TEST_CASE("NuclearStackExtension", "[stack]") {
       const auto& p29 = s.cbegin() + 29;
       const auto& p59 = s.cbegin() + 59;
 
-      REQUIRE(p29.GetPID() == particles::Code::Nucleus);
-      REQUIRE(p29.GetEnergy() == 59 * 15_GeV);
-      REQUIRE(p29.GetTime() == 100_s);
-      REQUIRE(p29.GetNuclearA() == 59);
-      REQUIRE(p29.GetNuclearZ() == 59 / 2);
-
-      REQUIRE(p59.GetPID() == particles::Code::Nucleus);
-      REQUIRE(p59.GetEnergy() == 29 * 15_GeV);
-      REQUIRE(p59.GetTime() == 100_s);
-      REQUIRE(p59.GetNuclearA() == 29);
-      REQUIRE(p59.GetNuclearZ() == 29 / 2);
+      CHECK(p29.GetPID() == particles::Code::Nucleus);
+      CHECK(p29.GetEnergy() == 59 * 15_GeV);
+      CHECK(p29.GetTime() == 100_s);
+      CHECK(p29.GetNuclearA() == 59);
+      CHECK(p29.GetNuclearZ() == 59 / 2);
+
+      CHECK(p59.GetPID() == particles::Code::Nucleus);
+      CHECK(p59.GetEnergy() == 29 * 15_GeV);
+      CHECK(p59.GetTime() == 100_s);
+      CHECK(p59.GetNuclearA() == 29);
+      CHECK(p59.GetNuclearZ() == 29 / 2);
     }
 
-    for (int i = 0; i < 99; ++i) s.DeleteLast();
-    REQUIRE(s.GetSize() == 0);
+    for (int i = 0; i < 99; ++i) s.last().Delete();
+    CHECK(s.getEntries() == 0);
   }
 }
diff --git a/Stack/SuperStupidStack/CMakeLists.txt b/Stack/SuperStupidStack/CMakeLists.txt
index 4edc8001c9276908ea569df2c0c601e4eeaff0e1..55b88c69c8c232a96c111e441426f4f23b8aca6a 100644
--- a/Stack/SuperStupidStack/CMakeLists.txt
+++ b/Stack/SuperStupidStack/CMakeLists.txt
@@ -12,6 +12,7 @@ target_link_libraries (
   CORSIKAunits
   CORSIKAparticles
   CORSIKAgeometry
+  CORSIKAlogging
   )
 
 target_include_directories (
@@ -33,8 +34,6 @@ install (
 CORSIKA_ADD_TEST(testSuperStupidStack)
 target_link_libraries (
   testSuperStupidStack
-  CORSIKAgeometry
-  CORSIKAparticles
-  CORSIKAunits
+  SuperStupidStack
   CORSIKAtesting
   )
diff --git a/Stack/SuperStupidStack/SuperStupidStack.h b/Stack/SuperStupidStack/SuperStupidStack.h
index 22cb88a6ceb2115e0e47bff38af127e29c8578a5..eae265cc72d5329cc8d18cad7b762609bee06e9e 100644
--- a/Stack/SuperStupidStack/SuperStupidStack.h
+++ b/Stack/SuperStupidStack/SuperStupidStack.h
@@ -16,7 +16,8 @@
 #include <corsika/geometry/RootCoordinateSystem.h> // remove
 #include <corsika/geometry/Vector.h>
 
-#include <algorithm>
+#include <string>
+#include <tuple>
 #include <vector>
 
 namespace corsika::stack {
@@ -40,6 +41,12 @@ namespace corsika::stack {
       using corsika::stack::ParticleBase<StackIteratorInterface>::GetIndex;
 
     public:
+      std::string as_string() const {
+        using namespace corsika::units::si;
+        return fmt::format("particle: i={}, PID={}, E={}GeV", GetIndex(),
+                           particles::GetName(GetPID()), GetEnergy() / 1_GeV);
+      }
+
       void SetParticleData(
           const std::tuple<corsika::particles::Code, corsika::units::si::HEPEnergyType,
                            MomentumVector, corsika::geometry::Point,
@@ -50,13 +57,6 @@ namespace corsika::stack {
         SetPosition(std::get<3>(v));
         SetTime(std::get<4>(v));
       }
-      /*
-    void SetParticleData(const corsika::particles::Code vDataPID,
-                         const corsika::units::si::HEPEnergyType vDataE,
-                         const MomentumVector& vMomentum,
-                         const corsika::geometry::Point& vPosition,
-                         const corsika::units::si::TimeType vTime) {
-      }*/
 
       void SetParticleData(
           ParticleInterface<StackIteratorInterface>&,
@@ -69,18 +69,6 @@ namespace corsika::stack {
         SetPosition(std::get<3>(v));
         SetTime(std::get<4>(v));
       }
-      /*      void SetParticleData(ParticleInterface<StackIteratorInterface>&,
-                           const corsika::particles::Code vDataPID,
-                           const corsika::units::si::HEPEnergyType vDataE,
-                           const MomentumVector& vMomentum,
-                           const corsika::geometry::Point& vPosition,
-                           const corsika::units::si::TimeType vTime) {
-        SetPID(vDataPID);
-        SetEnergy(vDataE);
-        SetMomentum(vMomentum);
-        SetPosition(vPosition);
-        SetTime(vTime);
-      }*/
 
       /// individual setters
       void SetPID(const corsika::particles::Code id) {
diff --git a/Stack/SuperStupidStack/testSuperStupidStack.cc b/Stack/SuperStupidStack/testSuperStupidStack.cc
index 04a892b50747d92c5e5e0d14e3da5eca984a0d75..a9f4fadc90d9d21125a92173b13b652e5aa7af90 100644
--- a/Stack/SuperStupidStack/testSuperStupidStack.cc
+++ b/Stack/SuperStupidStack/testSuperStupidStack.cc
@@ -6,6 +6,8 @@
  * the license.
  */
 
+#define protected public // to also test the internal state of objects
+
 #include <corsika/geometry/RootCoordinateSystem.h>
 #include <corsika/stack/super_stupid/SuperStupidStack.h>
 #include <corsika/units/PhysicalUnits.h>
@@ -18,7 +20,6 @@ using namespace corsika::units::si;
 using namespace corsika;
 using namespace corsika::stack::super_stupid;
 
-#include <iostream>
 using namespace std;
 
 TEST_CASE("SuperStupidStack", "[stack]") {
@@ -29,22 +30,18 @@ TEST_CASE("SuperStupidStack", "[stack]") {
   SECTION("read+write") {
 
     SuperStupidStack s;
-    s.AddParticle(std::tuple<corsika::particles::Code, corsika::units::si::HEPEnergyType,
-                             corsika::stack::MomentumVector, corsika::geometry::Point,
-                             corsika::units::si::TimeType>{
-        particles::Code::Electron, 1.5_GeV,
-        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
+    s.AddParticle(
+        std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                        corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
 
     // read
-    REQUIRE(s.GetSize() == 1);
+    CHECK(s.getEntries() == 1);
+    CHECK(s.getSize() == 1);
     auto pout = s.GetNextParticle();
-    REQUIRE(pout.GetPID() == particles::Code::Electron);
-    REQUIRE(pout.GetEnergy() == 1.5_GeV);
-    // REQUIRE(pout.GetMomentum() == stack::MomentumVector(dummyCS, {1_GeV,
-    // 1_GeV, 1_GeV})); REQUIRE(pout.GetPosition() == Point(dummyCS, {1 * meter, 1 *
-    // meter, 1 * meter}));
-    REQUIRE(pout.GetTime() == 100_s);
+    CHECK(pout.GetPID() == particles::Code::Electron);
+    CHECK(pout.GetEnergy() == 1.5_GeV);
+    CHECK(pout.GetTime() == 100_s);
   }
 
   SECTION("write+delete") {
@@ -52,17 +49,15 @@ TEST_CASE("SuperStupidStack", "[stack]") {
     SuperStupidStack s;
     for (int i = 0; i < 99; ++i)
       s.AddParticle(
-          std::tuple<corsika::particles::Code, corsika::units::si::HEPEnergyType,
-                     corsika::stack::MomentumVector, corsika::geometry::Point,
-                     corsika::units::si::TimeType>{
-              particles::Code::Electron, 1.5_GeV,
-              corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
-              Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s});
+          std::make_tuple(particles::Code::Electron, 1.5_GeV,
+                          corsika::stack::MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                          Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
 
-    REQUIRE(s.GetSize() == 99);
+    CHECK(s.getSize() == 99);
 
     for (int i = 0; i < 99; ++i) s.GetNextParticle().Delete();
 
-    REQUIRE(s.GetSize() == 0);
+    CHECK(s.getEntries() == 0);
+    CHECK(s.getSize() == 1);
   }
 }
diff --git a/ThirdParty/.gitignore b/ThirdParty/.gitignore
index b4bac0ecf395dd655e583e048902f92da63774ce..0d7fe279527897921a3d07f84e8f70c1dd05ffac 100644
--- a/ThirdParty/.gitignore
+++ b/ThirdParty/.gitignore
@@ -1,2 +1 @@
 eigen-eigen-b3f3d4950030/
-
diff --git a/ThirdParty/CMakeLists.txt b/ThirdParty/CMakeLists.txt
index 27c166eb9b76fed84519a37dbf735db0f4c1646f..19cc00b37ffb1b15d30c7ea97f6b79d554d8b7df 100644
--- a/ThirdParty/CMakeLists.txt
+++ b/ThirdParty/CMakeLists.txt
@@ -1,3 +1,5 @@
+add_subdirectory (spdlog) # this is a git submodule 
+
 add_library (CORSIKAthirdparty INTERFACE)
 
 target_include_directories (CORSIKAthirdparty SYSTEM
@@ -16,9 +18,6 @@ set (ThirdPartyChoiceValues "C8;SYSTEM" CACHE STRING
     "List of possible values for the ThirdParty package choice")
 mark_as_advanced (ThirdPartyChoiceValues)
 
-##############################################################################
-# Build spdlog
-add_subdirectory (spdlog)
 
 ##############################################################################
 # check for boost: either use C8 or system-level installation
diff --git a/Tools/read_hist.py b/Tools/read_hist.py
index 4348e118bdcb4fe4bc3e11fcd50b626425238788..53fbf0871679c5846ba4884125dab41855d58191 100755
--- a/Tools/read_hist.py
+++ b/Tools/read_hist.py
@@ -1,5 +1,4 @@
 import numpy as np
-import matplotlib.pyplot as plt
 import boost_histogram as bh
 import operator
 import functools
diff --git a/do-clang-format.py b/do-clang-format.py
index ed5938ae3b2efe7676c14c9e524e48d9db9e3651..e3406502846f41ffed9f73aba88e7d828851bb8a 100755
--- a/do-clang-format.py
+++ b/do-clang-format.py
@@ -28,7 +28,7 @@ if args.all:
         if doExclude:
             continue
         for f in filenames:
-            if f.endswith(".h") or f.endswith(".cc"):
+            if f.endswith(".h") or f.endswith(".cc") or f.endswith(".hpp") or f.endswith(".cpp") or f.endswith(".cxx"):
                 filename = os.path.join(dirpath, f)
                 if not os.path.islink(filename):
                     filelist.append(filename)
@@ -52,7 +52,7 @@ else:
 cmd = "clang-format"
 if "CLANG_FORMAT" in os.environ:
   cmd = os.environ["CLANG_FORMAT"]
-  cmd +=  " -style=file"
+cmd +=  " -style=file"
 if args.apply:
     for filename in filelist:
         subp.check_call(cmd.split() + ["-i", filename])