diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl
new file mode 100644
index 0000000000000000000000000000000000000000..6f87779ecb0d46c84a3733b9f2e3997ddab2fb73
--- /dev/null
+++ b/corsika/detail/framework/process/SwitchProcessSequence.inl
@@ -0,0 +1,291 @@
+/*
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/framework/process/BaseProcess.hpp>
+#include <corsika/framework/process/ProcessTraits.hpp>
+#include <corsika/framework/process/BoundaryCrossingProcess.hpp>
+#include <corsika/framework/process/ContinuousProcess.hpp>
+#include <corsika/framework/process/DecayProcess.hpp>
+#include <corsika/framework/process/InteractionProcess.hpp>
+#include <corsika/framework/process/ProcessReturn.hpp>
+#include <corsika/framework/process/SecondariesProcess.hpp>
+#include <corsika/framework/process/StackProcess.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <cmath>
+#include <limits>
+#include <type_traits>
+
+namespace corsika {
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TParticle, typename TVTNType>
+  EProcessReturn SwitchProcessSequence<TProcess1, TProcess2, TSelect>::doBoundaryCrossing(
+      TParticle& particle, TVTNType const& from, TVTNType const& to) {
+    switch (select_(particle)) {
+      case SwitchResult::First: {
+        if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>,
+                                        process1_type> ||
+                      t1ProcSeq) {
+          return A_.doBoundaryCrossing(particle, from, to);
+        }
+        break;
+      }
+      case SwitchResult::Second: {
+        if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>,
+                                        process2_type> ||
+                      t2ProcSeq) {
+          return B_.doBoundaryCrossing(particle, from, to);
+        }
+        break;
+      }
+    }
+    return EProcessReturn::eOk;
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TParticle, typename TTrack>
+  inline EProcessReturn
+  SwitchProcessSequence<TProcess1, TProcess2, TSelect>::doContinuous(TParticle& particle,
+                                                                     TTrack& vT) {
+    switch (select_(particle)) {
+      case SwitchResult::First: {
+        if constexpr (std::is_base_of_v<ContinuousProcess<process1_type>,
+                                        process1_type> ||
+                      t1ProcSeq) {
+          return A_.doContinuous(particle, vT);
+        }
+        break;
+      }
+      case SwitchResult::Second: {
+        if constexpr (std::is_base_of_v<ContinuousProcess<process2_type>,
+                                        process2_type> ||
+                      t2ProcSeq) {
+          return B_.doContinuous(particle, vT);
+        }
+        break;
+      }
+    }
+    return EProcessReturn::eOk;
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TSecondaries>
+  inline void SwitchProcessSequence<TProcess1, TProcess2, TSelect>::doSecondaries(
+      TSecondaries& vS) {
+    const auto& particle = vS.parent();
+    switch (select_(particle)) {
+      case SwitchResult::First: {
+        if constexpr (std::is_base_of_v<SecondariesProcess<process1_type>,
+                                        process1_type> ||
+                      t1ProcSeq) {
+          A_.doSecondaries(vS);
+        }
+        break;
+      }
+      case SwitchResult::Second: {
+        if constexpr (std::is_base_of_v<SecondariesProcess<process2_type>,
+                                        process2_type> ||
+                      t2ProcSeq) {
+          B_.doSecondaries(vS);
+        }
+        break;
+      }
+    }
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TParticle, typename TTrack>
+  inline LengthType SwitchProcessSequence<TProcess1, TProcess2, TSelect>::maxStepLength(
+      TParticle& particle, TTrack& vTrack) {
+    switch (select_(particle)) {
+      case SwitchResult::First: {
+        if constexpr (std::is_base_of_v<ContinuousProcess<process1_type>,
+                                        process1_type> ||
+                      t1ProcSeq) {
+          return A_.maxStepLength(particle, vTrack);
+        }
+        break;
+      }
+      case SwitchResult::Second: {
+        if constexpr (std::is_base_of_v<ContinuousProcess<process2_type>,
+                                        process2_type> ||
+                      t2ProcSeq) {
+          return B_.maxStepLength(particle, vTrack);
+        }
+        break;
+      }
+    }
+
+    // if no other process in the sequence implements it
+    return std::numeric_limits<double>::infinity() * meter;
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TParticle>
+  inline InverseGrammageType
+  SwitchProcessSequence<TProcess1, TProcess2, TSelect>::getInverseInteractionLength(
+      TParticle&& particle) {
+
+    switch (select_(particle)) {
+      case SwitchResult::First: {
+        if constexpr (std::is_base_of_v<InteractionProcess<process1_type>,
+                                        process1_type> ||
+                      t1ProcSeq) {
+          return A_.getInverseInteractionLength(particle);
+        }
+        break;
+      }
+      case SwitchResult::Second: {
+        if constexpr (std::is_base_of_v<InteractionProcess<process2_type>,
+                                        process2_type> ||
+                      t2ProcSeq) {
+          return B_.getInverseInteractionLength(particle);
+        }
+        break;
+      }
+    }
+    return 0 * meter * meter / gram; // default value
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TSecondaryView>
+  inline EProcessReturn
+  SwitchProcessSequence<TProcess1, TProcess2, TSelect>::selectInteraction(
+      TSecondaryView& view, [[maybe_unused]] InverseGrammageType lambda_inv_select,
+      [[maybe_unused]] InverseGrammageType lambda_inv_sum) {
+    switch (select_(view.parent())) {
+      case SwitchResult::First: {
+        if constexpr (t1ProcSeq) {
+          // if A_ is a process sequence --> check inside
+          EProcessReturn const ret =
+              A_.selectInteraction(view, lambda_inv_select, lambda_inv_sum);
+          // if A_ did succeed, stop routine. Not checking other static branch B_.
+          if (ret != EProcessReturn::eOk) { return ret; }
+        } else if constexpr (std::is_base_of_v<InteractionProcess<process1_type>,
+                                               process1_type>) {
+          // if this is not a ContinuousProcess --> evaluate probability
+          lambda_inv_sum += A_.getInverseInteractionLength(view.parent());
+          // check if we should execute THIS process and then EXIT
+          if (lambda_inv_select < lambda_inv_sum) {
+            A_.doInteraction(view);
+            return EProcessReturn::eInteracted;
+          }
+        } // end branch A_
+        break;
+      }
+
+      case SwitchResult::Second: {
+
+        if constexpr (t2ProcSeq) {
+          // if B_ is a process sequence --> check inside
+          return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum);
+        } else if constexpr (std::is_base_of_v<InteractionProcess<process2_type>,
+                                               process2_type>) {
+          // if this is not a ContinuousProcess --> evaluate probability
+          lambda_inv_sum += B_.getInverseInteractionLength(view.parent());
+          // check if we should execute THIS process and then EXIT
+          if (lambda_inv_select < lambda_inv_sum) {
+            B_.doInteraction(view);
+            return EProcessReturn::eInteracted;
+          }
+        } // end branch B_
+        break;
+      }
+    }
+    return EProcessReturn::eOk;
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  template <typename TParticle>
+  inline InverseTimeType
+  SwitchProcessSequence<TProcess1, TProcess2, TSelect>::getInverseLifetime(
+      TParticle&& particle) {
+
+    switch (select_(particle)) {
+      case SwitchResult::First: {
+        if constexpr (std::is_base_of_v<DecayProcess<process1_type>, process1_type> ||
+                      t1ProcSeq) {
+          return A_.getInverseLifetime(particle);
+        }
+        break;
+      }
+
+      case SwitchResult::Second: {
+        if constexpr (std::is_base_of_v<DecayProcess<process2_type>, process2_type> ||
+                      t2ProcSeq) {
+          return B_.getInverseLifetime(particle);
+        }
+        break;
+      }
+    }
+    return 0 / second; // default value
+  }
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  // select decay process
+  template <typename TSecondaryView>
+  inline EProcessReturn SwitchProcessSequence<TProcess1, TProcess2, TSelect>::selectDecay(
+      TSecondaryView& view, [[maybe_unused]] InverseTimeType decay_inv_select,
+      [[maybe_unused]] InverseTimeType decay_inv_sum) {
+    switch (select_(view.parent())) {
+      case SwitchResult::First: {
+        if constexpr (t1ProcSeq) {
+          // if A_ is a process sequence --> check inside
+          EProcessReturn const ret =
+              A_.selectDecay(view, decay_inv_select, decay_inv_sum);
+          // if A_ did succeed, stop routine here (not checking other static branch B_)
+          if (ret != EProcessReturn::eOk) { return ret; }
+        } else if constexpr (std::is_base_of_v<DecayProcess<process1_type>,
+                                               process1_type>) {
+          // if this is not a ContinuousProcess --> evaluate probability
+          decay_inv_sum += A_.getInverseLifetime(view.parent());
+          // check if we should execute THIS process and then EXIT
+          if (decay_inv_select < decay_inv_sum) {
+            // more pedagogical: rndm_select < decay_inv_sum / decay_inv_tot
+            A_.doDecay(view);
+            return EProcessReturn::eDecayed;
+          }
+        } // end branch A_
+        break;
+      }
+
+      case SwitchResult::Second: {
+
+        if constexpr (t2ProcSeq) {
+          // if B_ is a process sequence --> check inside
+          return B_.selectDecay(view, decay_inv_select, decay_inv_sum);
+        } else if constexpr (std::is_base_of_v<DecayProcess<process2_type>,
+                                               process2_type>) {
+          // if this is not a ContinuousProcess --> evaluate probability
+          decay_inv_sum += B_.getInverseLifetime(view.parent());
+          // check if we should execute THIS process and then EXIT
+          if (decay_inv_select < decay_inv_sum) {
+            B_.doDecay(view);
+            return EProcessReturn::eDecayed;
+          }
+        } // end branch B_
+        break;
+      }
+    }
+    return EProcessReturn::eOk;
+  }
+
+  /// traits marker to identify objectas ProcessSequence
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  struct is_process_sequence<SwitchProcessSequence<TProcess1, TProcess2, TSelect>>
+      : std::true_type {};
+
+  /// traits marker to identify objectas SwitchProcessSequence
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  struct is_switch_process_sequence<SwitchProcessSequence<TProcess1, TProcess2, TSelect>>
+      : std::true_type {};
+
+} // namespace corsika
diff --git a/corsika/framework/process/NullModel.hpp b/corsika/framework/process/NullModel.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..51678a977d6a362b7e95310e06edd699a3d196d7
--- /dev/null
+++ b/corsika/framework/process/NullModel.hpp
@@ -0,0 +1,22 @@
+/*
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/framework/process/BaseProcess.hpp>
+
+namespace corsika {
+
+  class NullModel : public BaseProcess<NullModel> {
+
+  public:
+    NullModel() = default;
+    ~NullModel() = default;
+  };
+
+} // namespace corsika
diff --git a/corsika/framework/process/ProcessTraits.hpp b/corsika/framework/process/ProcessTraits.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2d3598595f6bdc345c43fa233835ba79611dac75
--- /dev/null
+++ b/corsika/framework/process/ProcessTraits.hpp
@@ -0,0 +1,43 @@
+/*
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+namespace corsika {
+
+  /**
+   *  A traits marker to track which BaseProcess is also a ProcessSequence
+   **/
+  template <typename TClass>
+  struct is_process_sequence : std::false_type {};
+
+  template <typename TClass>
+  bool constexpr is_process_sequence_v = is_process_sequence<TClass>::value;
+
+  /**
+   * A traits marker to identiy a BaseProcess that is also SwitchProcessesSequence
+   **/
+
+  template <typename TClass>
+  struct is_switch_process_sequence : std::false_type {};
+
+  template <typename TClass>
+  bool constexpr is_switch_process_sequence_v = is_switch_process_sequence<TClass>::value;
+
+  /**
+   * A traits marker to identify ProcessSequence that contain a StackProcess
+   **/
+  template <typename TClass>
+  struct contains_stack_process : std::false_type {};
+
+  template <typename TClass>
+  bool constexpr contains_stack_process_v = contains_stack_process<TClass>::value;
+
+} // namespace corsika
diff --git a/corsika/framework/process/SwitchProcessSequence.hpp b/corsika/framework/process/SwitchProcessSequence.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..aa772649f771709e27399b115b81ab5d96bc1242
--- /dev/null
+++ b/corsika/framework/process/SwitchProcessSequence.hpp
@@ -0,0 +1,168 @@
+/*
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/framework/process/BaseProcess.hpp>
+#include <corsika/framework/process/ProcessTraits.hpp>
+#include <corsika/framework/process/BoundaryCrossingProcess.hpp>
+#include <corsika/framework/process/ContinuousProcess.hpp>
+#include <corsika/framework/process/DecayProcess.hpp>
+#include <corsika/framework/process/InteractionProcess.hpp>
+#include <corsika/framework/process/ProcessReturn.hpp>
+#include <corsika/framework/process/SecondariesProcess.hpp>
+#include <corsika/framework/process/StackProcess.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <cmath>
+#include <limits>
+#include <type_traits>
+
+namespace corsika {
+
+  // enum for the process switch selection: identify if First or
+  // Second process branch should be used.
+  enum class SwitchResult { First, Second };
+
+  /**
+     \class SwitchProcessSequence
+
+     A compile time static list of processes that uses an internal
+     TSelect class to switch between different versions of processes
+     (or process sequence).
+
+     TProcess1 and TProcess2 must be derived from BaseProcess and are
+     both references if possible (lvalue), otherwise (rvalue) they are
+     just classes. This allows us to handle both, rvalue as well as
+     lvalue Processes in the SwitchProcessSequence.
+
+     TSelect has to implement a `operator()(const Particle&)` and has to
+     return either SwitchResult::First or SwitchResult::Second. Note:
+     TSelect may absolutely also use random numbers to sample between
+     its results. This can be used to achieve arbitrarily smooth
+     transition or mixtures of processes.
+
+     Warning: do not put StackProcess into a SwitchProcessSequence
+     since this makes no sense. The StackProcess acts on an entire
+     particle stack and not on indiviidual particles.
+
+     \comment See also class ProcessSequence
+  **/
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  class SwitchProcessSequence
+      : public BaseProcess<SwitchProcessSequence<TProcess1, TProcess2, TSelect>> {
+
+    using process1_type = typename std::decay_t<TProcess1>;
+    using process2_type = typename std::decay_t<TProcess2>;
+
+    static bool constexpr t1ProcSeq = is_process_sequence_v<process1_type>;
+    static bool constexpr t2ProcSeq = is_process_sequence_v<process2_type>;
+
+    // make sure only BaseProcess types TProcess1/2 are passed
+    static_assert(std::is_base_of_v<BaseProcess<process1_type>, process1_type>,
+                  "can only use process derived from BaseProcess in "
+                  "SwitchProcessSequence, for Process 1");
+    static_assert(std::is_base_of_v<BaseProcess<process2_type>, process2_type>,
+                  "can only use process derived from BaseProcess in "
+                  "SwitchProcessSequence, for Process 2");
+
+    // make sure none of TProcess1/2 is a StackProcess
+    static_assert(!std::is_base_of_v<StackProcess<process1_type>, process1_type>,
+                  "cannot use StackProcess in SwitchProcessSequence, for Process 1");
+    static_assert(!std::is_base_of_v<StackProcess<process2_type>, process2_type>,
+                  "cannot use StackProcess in SwitchProcessSequence, for Process 2");
+
+    // if TProcess1/2 are already ProcessSequences, make sure they do not contain
+    // any StackProcess
+    static_assert(!contains_stack_process_v<process1_type>,
+                  "cannot use StackProcess in SwitchProcessSequence, remove from "
+                  "ProcessSequence 1");
+    static_assert(!contains_stack_process_v<process2_type>,
+                  "cannot use StackProcess in SwitchProcessSequence, remove from "
+                  "ProcessSequence 2");
+
+    TSelect select_; // this is a reference, if possible
+
+    TProcess1 A_; // this is a reference, if possible
+    TProcess2 B_; // this is a reference, if possible
+
+  public:
+    SwitchProcessSequence(TProcess1 in_A, TProcess2 in_B, TSelect sel)
+        : select_(sel)
+        , A_(in_A)
+        , B_(in_B) {}
+
+    template <typename TParticle, typename TVTNType>
+    EProcessReturn doBoundaryCrossing(TParticle& particle, TVTNType const& from,
+                                      TVTNType const& to);
+
+    template <typename TParticle, typename TTrack>
+    inline EProcessReturn doContinuous(TParticle& particle, TTrack& vT);
+
+    template <typename TSecondaries>
+    inline void doSecondaries(TSecondaries& vS);
+
+    template <typename TParticle, typename TTrack>
+    inline LengthType maxStepLength(TParticle& particle, TTrack& vTrack);
+
+    template <typename TParticle>
+    inline GrammageType getInteractionLength(TParticle&& particle) {
+      return 1. / getInverseInteractionLength(particle);
+    }
+
+    template <typename TParticle>
+    inline InverseGrammageType getInverseInteractionLength(TParticle&& particle);
+
+    template <typename TSecondaryView>
+    inline EProcessReturn selectInteraction(
+        TSecondaryView& view, [[maybe_unused]] InverseGrammageType lambda_inv_select,
+        [[maybe_unused]] InverseGrammageType lambda_inv_sum =
+            InverseGrammageType::zero());
+
+    template <typename TParticle>
+    inline TimeType getLifetime(TParticle&& particle) {
+      return 1. / getInverseLifetime(particle);
+    }
+
+    template <typename TParticle>
+    inline InverseTimeType getInverseLifetime(TParticle&& particle);
+
+    // select decay process
+    template <typename TSecondaryView>
+    inline EProcessReturn selectDecay(
+        TSecondaryView& view, [[maybe_unused]] InverseTimeType decay_inv_select,
+        [[maybe_unused]] InverseTimeType decay_inv_sum = InverseTimeType::zero());
+  };
+
+  /**
+   * \function make_select
+   *
+   * the functin `make_select(proc1,proc1,selector)` assembles many
+   * BaseProcesses, and ProcessSequences into a SwitchProcessSequence,
+   * all combinatorics must be allowed, this is why we define a macro
+   * to define all combinations here:
+   *
+   *
+   * Both, Processes1 and Processes2, must derive from BaseProcesses
+   **/
+
+  template <typename TProcess1, typename TProcess2, typename TSelect>
+  inline typename std::enable_if_t<
+      std::is_base_of_v<BaseProcess<typename std::decay_t<TProcess1>>,
+                        typename std::decay_t<TProcess1>> &&
+          std::is_base_of_v<BaseProcess<typename std::decay_t<TProcess2>>,
+                            typename std::decay_t<TProcess2>>,
+      SwitchProcessSequence<TProcess1, TProcess2, TSelect>>
+  make_select(TProcess1&& vA, TProcess2&& vB, TSelect selector) {
+    return SwitchProcessSequence<TProcess1, TProcess2, TSelect>(vA, vB, selector);
+  }
+
+} // namespace corsika
+
+#include <corsika/detail/framework/process/SwitchProcessSequence.inl>
diff --git a/tests/framework/testNullModel.cpp b/tests/framework/testNullModel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2db1090994525357246ee67ffb0e4cea844c12ab
--- /dev/null
+++ b/tests/framework/testNullModel.cpp
@@ -0,0 +1,23 @@
+/*
+ * (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/framework/process/NullModel.hpp>
+
+#include <catch2/catch.hpp>
+
+using namespace corsika;
+
+/*
+ * The NullModel can do really nothing, so we can basically test
+ * nothing.
+ */
+
+TEST_CASE("NullModel", "[processes]") {
+
+  SECTION("interface") { [[maybe_unused]] NullModel model; }
+}