diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fe2865b43182d3afac7a1513212bcd1cde988e13..efb24de0f82713e50803daa627e6d8c27e1c8de4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -105,25 +105,16 @@ check-clang-format:
     expire_in: 3 days
     paths:
       - ${CI_PROJECT_DIR}/build/CMakeFiles/CMakeOutput.log
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull-push    
 
 # config for gcc
 config-u-18_04:
   extends: .config
   image: corsika/devel:u-18.04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
 
 # config for clang
 config-clang-8:
   extends: .config
   image: corsika/devel:clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
 
 
 
@@ -138,7 +129,9 @@ config-clang-8:
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
     - cmake --build . -- -j4
   rules:
     - if: $CI_MERGE_REQUEST_ID
@@ -148,30 +141,22 @@ config-clang-8:
     - if: $CI_COMMIT_BRANCH
       when: manual
   allow_failure: true
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull-push
 
 # build for gcc
 build-u-18_04:
   extends: .build
   image: corsika/devel:u-18.04
-  dependencies:
-    - config-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs: 
+    - job: config-u-18_04
+      artifacts: false
 
 # build for clang
 build-clang-8:
   extends: .build
   image: corsika/devel:clang-8
-  dependencies:
-    - config-clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
-
+  needs:
+    - job: config-clang-8
+      artifacts: false
 
 
 
@@ -185,7 +170,9 @@ build-clang-8:
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
     - set -o pipefail
     - ctest -j4 
   rules:
@@ -202,29 +189,22 @@ build-clang-8:
     reports:
       junit:
         - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull-push
 
 # test for gcc
 test-u-18_04:
   extends: .test
   image: corsika/devel:u-18.04
-  dependencies:
-    - build-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs:
+    - job: build-u-18_04
+      artifacts: false
 
 # test for clang
 test-clang-8:
   extends: .test
   image: corsika/devel:clang-8
-  dependencies:
-    - build-clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
+  needs:
+    - job: build-clang-8
+      artifacts: false
 
 
 
@@ -239,7 +219,9 @@ test-clang-8:
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
     - cmake --build . -- -j4
     - set -o pipefail
     - ctest -j4 
@@ -257,29 +239,22 @@ test-clang-8:
     reports:
       junit:
         - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull-push
 
 # build_test for gcc
 build_test-u-18_04:
   extends: .build_test
   image: corsika/devel:u-18.04
-  dependencies:
-    - config-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs:
+    - job: config-u-18_04
+      artifacts: false
 
 # build_test for clang
 build_test-clang-8:
   extends: .build_test
   image: corsika/devel:clang-8
-  dependencies:
-    - config-clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
+  needs:
+    - job: config-clang-8
+      artifacts: false
 
 
 
@@ -295,14 +270,16 @@ build_test-clang-8:
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
     - cmake --build . -- -j4
     - set -o pipefail
     - ctest -j4
     - make install
     - mkdir -p build_examples
     - cd build_examples
-    - cmake ../install/share/corsika/examples
+    - cmake -DCMAKE_BUILT_TYPE=Debug ../install/share/corsika/examples
     - make -j4
     - make -j4 run_examples | gzip -v -9 > examples.log.gz
   rules:
@@ -323,29 +300,23 @@ build_test-clang-8:
     paths:
       - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz
       - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull
 
 # build_test_example for gcc
 build_test_example-u-18_04:
   extends: .build_test_example
   image: corsika/devel:u-18.04
-  dependencies:
-    - config-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs:
+    - job: config-u-18_04
+      artifacts: false
+
 
 # build_test_example for clang
 build_test_example-clang-8:
   extends: .build_test_example
   image: corsika/devel:clang-8
-  dependencies:
-    - config-clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
+  needs:
+    - job: config-clang-8
+      artifacts: false
 
 
 
@@ -360,7 +331,9 @@ build_test_example-clang-8:
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
     - set -o pipefail
     - make -j2 install
   rules:
@@ -374,29 +347,22 @@ build_test_example-clang-8:
     - if: $CI_COMMIT_TAG
       when: manual
       allow_failure: true
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull
 
 # install for gcc
 install-u-18_04:
   extends: .install
   image: corsika/devel:u-18.04
-  dependencies:
-    - build-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs:
+    - job: build-u-18_04
+      artifacts: false
 
 # install for clang
 install-clang-8:
   extends: .install
   image: corsika/devel:clang-8
-  dependencies:
-    - build-clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
+  needs:
+    - job: build-clang-8
+      artifacts: false
 
 
 
@@ -411,15 +377,16 @@ install-clang-8:
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
-    - cmake .. -DCMAKE_BUILD_TYPE=Release -DUSE_Pythia8_C8=C8
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Release -DUSE_Pythia8_C8=C8
     - cmake --build . -- -j4
     - set -o pipefail
     - ctest -j4
     - make install
     - mkdir -p build_examples
     - cd build_examples
-    - cmake ../install/share/corsika/examples
+    - cmake -DCMAKE_BUILT_TYPE=Release ../install/share/corsika/examples
     - make -j4
     - make -j4 run_examples | gzip -v -9 > examples.log.gz
   rules:
@@ -433,11 +400,6 @@ install-clang-8:
     - if: $CI_COMMIT_TAG
       when: manual
       allow_failure: true
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull
   artifacts:
     when: always
     expire_in: 3 days
@@ -452,19 +414,17 @@ install-clang-8:
 release-full-u-18_04:
   extends: .release
   image: corsika/devel:u-18.04
-  dependencies:
-    - config-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs:
+    - job: config-u-18_04
+      artifacts: false
 
 # release for clang
 release-full-clang-8:
   extends: .release
   image: corsika/devel:clang-8
-  dependencies:
-    - config-clang-8
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang"
+  needs:
+    - job: config-clang-8
+      artifacts: false
 
 
 
@@ -474,12 +434,14 @@ release-full-clang-8:
 
 coverage:
   image: corsika/devel:u-18.04
-  dependencies:
-    - config-u-18_04
+  needs:
+    - job: config-u-18_04
+      artifacts: false
   stage: optional
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
     - cmake .. -DCMAKE_BUILD_TYPE=Coverage -DUSE_Pythia8_C8=C8
     - cmake --build . -- -j4
@@ -501,12 +463,6 @@ coverage:
     expire_in: 1 year
     paths:
       - ${CI_PROJECT_DIR}/build/coverage-report.tar.gz
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
 
 
 
@@ -517,14 +473,17 @@ coverage:
 ##########################################################
 sanity:
   image: corsika/devel:u-18.04
-  dependencies:
-    - config-u-18_04
+  needs:
+    - job: config-u-18_04
+      artifacts: false
+
   stage: optional
   tags:
     - corsika
   script:
+    - mkdir -p build
     - cd build
-    - cmake .. -DWITH_CORSIKA_SANITIZERS_ENABLED=ON
+    - cmake .. -DWITH_CORSIKA_SANITIZERS_ENABLED=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
     - cmake --build . -- -j4
   rules:
     - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set
@@ -537,12 +496,6 @@ sanity:
     - if: $CI_COMMIT_TAG
       when: manual
       allow_failure: true
-  cache:
-    paths:
-      - ${CI_PROJECT_DIR}/build/
-    untracked: true
-    policy: pull
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
 
 ##########################################################
 # template for all Python jobs
@@ -569,10 +522,9 @@ sanity:
 python-3.8:
   extends: .python
   image: corsika/devel:u-18.04
-  dependencies:
-    - build-u-18_04
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  needs:
+    - job: build-u-18_04
+      artifacts: false
   artifacts:
     when: always
     expire_in: 1 year
diff --git a/.gitlab-ci.yml-cache b/.gitlab-ci.yml-cache
new file mode 100644
index 0000000000000000000000000000000000000000..fe2865b43182d3afac7a1513212bcd1cde988e13
--- /dev/null
+++ b/.gitlab-ci.yml-cache
@@ -0,0 +1,581 @@
+variables:
+  GIT_STRATEGY: $CORSIKA8_GIT_STRATEGY # clone: fresh clone, fetch: update
+  GIT_SSL_NO_VERIFY: "1"
+  GIT_DEPTH: "1"
+  # to re-use clones also in different forks
+  GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_NAME
+  ## Runtime options for sanitizers
+  # (detect_leaks=0 because leak detection doesn't work in CI, but you can
+  # try to test with leak detection locally by using detect_leaks=1)
+  UBSAN_OPTIONS: "print_stacktrace=1"
+  LSAN_OPTIONS: "log_threads=1"
+  ASAN_OPTIONS: "detect_leaks=0:detect_stack_use_after_return=1"
+  # location of AirShowerPhysics/corsika-data
+  CORSIKA_DATA: "${CI_PROJECT_DIR}/modules/data" # the git submodule
+  corsika_DIR: "${CI_PROJECT_DIR}/build/install" # for cmake to find corsikaConfig.cmake
+  # _alternatively_ corsika-data can be downloaded as submodule:
+  GIT_SUBMODULE_STRATEGY: normal # none: we get the submodules in before_script,
+                                 # normal: get submodules automatically
+  CTEST_OUTPUT_ON_FAILURE: 1
+
+
+#
+# multi-step pipeline for each commit
+#
+# Mote: "Draft:" merge request, non-Draft merge requests and "Ready for Code Review" MR are all
+#       handled explicitly
+#
+stages:
+  - quality
+  - config
+  - build
+  - test
+  - build_test
+  - example
+  - build_test_example
+  - python
+  - install
+  - optional
+
+
+
+
+####### CODE QUALITY CHECK ##############
+
+##########################################################
+check-copyrights:
+  image: corsika/devel:u-18.04
+  stage: quality
+  tags:
+    - corsika
+  script:
+    - ./do-copyright.py
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+    - if: $CI_COMMIT_TAG
+    - if: $CI_COMMIT_BRANCH
+  allow_failure: true
+
+##########################################################
+check-clang-format:
+  image: corsika/devel:clang-8
+  stage: quality
+  tags:
+    - corsika
+  script:
+    - ./do-clang-format.py --all
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+    - if: $CI_COMMIT_TAG
+    - if: $CI_COMMIT_BRANCH
+  allow_failure: true
+
+### CodeQuality tool ####
+#include:
+#  - template: Code-Quality.gitlab-ci.yml
+#
+#code_quality:
+#  stage: quality
+#  rules:
+#    - if: '$CODE_QUALITY_DISABLED'
+#      when: never
+#    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # Run code quality job in merge request pipelines
+#    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'      # Run code quality job in pipelines on the master branch (but not in other branch pipelines)
+#    - if: '$CI_COMMIT_TAG'                               # Run code quality job in pipelines for tags
+
+####### CONFIG ##############
+
+##########################################################
+# the generic config template job
+# job/stage to just prepare cmake
+.config:
+  stage: config
+  tags:
+    - corsika
+  script:
+    - mkdir -p build
+    - cd build
+    - cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+    - if: $CI_COMMIT_TAG
+    - if: $CI_COMMIT_BRANCH
+  artifacts:
+    when: on_failure
+    expire_in: 3 days
+    paths:
+      - ${CI_PROJECT_DIR}/build/CMakeFiles/CMakeOutput.log
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull-push    
+
+# config for gcc
+config-u-18_04:
+  extends: .config
+  image: corsika/devel:u-18.04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# config for clang
+config-clang-8:
+  extends: .config
+  image: corsika/devel:clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+####### BUILD (only manual) ##############
+
+##########################################################
+# the generic build template job
+# normal pipeline for each commit
+.build:
+  stage: build
+  tags:
+    - corsika
+  script:
+    - cd build
+    - cmake --build . -- -j4
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+    - if: $CI_COMMIT_TAG
+      when: manual
+    - if: $CI_COMMIT_BRANCH
+      when: manual
+  allow_failure: true
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull-push
+
+# build for gcc
+build-u-18_04:
+  extends: .build
+  image: corsika/devel:u-18.04
+  dependencies:
+    - config-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# build for clang
+build-clang-8:
+  extends: .build
+  image: corsika/devel:clang-8
+  dependencies:
+    - config-clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+####### TEST (only manual)  ##############
+
+##########################################################
+# generic test template job
+# normal pipeline for each commit
+.test:
+  stage: test
+  tags:
+    - corsika
+  script:
+    - cd build
+    - set -o pipefail
+    - ctest -j4 
+  rules:
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+    - if: $CI_COMMIT_TAG
+      when: manual
+    - if: $CI_COMMIT_BRANCH
+      when: manual
+  allow_failure: true
+  artifacts:
+    when: always
+    expire_in: 3 days
+    reports:
+      junit:
+        - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull-push
+
+# test for gcc
+test-u-18_04:
+  extends: .test
+  image: corsika/devel:u-18.04
+  dependencies:
+    - build-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# test for clang
+test-clang-8:
+  extends: .test
+  image: corsika/devel:clang-8
+  dependencies:
+    - build-clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+####### BUILD-TEST (all builds <= Draft, default) ##############
+
+##########################################################
+# the generic build_test template job
+# normal pipeline for each commit
+.build_test:
+  stage: build_test
+  tags:
+    - corsika
+  script:
+    - cd build
+    - cmake --build . -- -j4
+    - set -o pipefail
+    - ctest -j4 
+  rules:
+    - if: '$CI_MERGE_REQUEST_ID && $CI_MERGE_REQUEST_TITLE =~ /^Draft:/'
+      allow_failure: false
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+      allow_failure: true
+  artifacts:
+    when: always
+    expire_in: 3 days
+    paths:
+      - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+    reports:
+      junit:
+        - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull-push
+
+# build_test for gcc
+build_test-u-18_04:
+  extends: .build_test
+  image: corsika/devel:u-18.04
+  dependencies:
+    - config-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# build_test for clang
+build_test-clang-8:
+  extends: .build_test
+  image: corsika/devel:clang-8
+  dependencies:
+    - config-clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+
+####### BUILD-TEST-EXAMPLE (only non-Draft)  ##############
+
+##########################################################
+# generic example template job
+# normal pipeline for each commit
+.build_test_example:
+  stage: build_test_example
+  tags:
+    - corsika
+  script:
+    - cd build
+    - cmake --build . -- -j4
+    - set -o pipefail
+    - ctest -j4
+    - make install
+    - mkdir -p build_examples
+    - cd build_examples
+    - cmake ../install/share/corsika/examples
+    - make -j4
+    - make -j4 run_examples | gzip -v -9 > examples.log.gz
+  rules:
+    - if: '$CI_MERGE_REQUEST_ID && $CI_MERGE_REQUEST_TITLE =~ /^Draft:/'
+      when: manual
+      allow_failure: true
+    - if: $CI_COMMIT_BRANCH
+    - if: $CI_MERGE_REQUEST_ID
+    - if: $CI_COMMIT_TAG
+      when: manual
+      allow_failure: true
+  artifacts:
+    when: always
+    expire_in: 3 days
+    reports:
+      junit:
+        - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+    paths:
+      - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz
+      - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull
+
+# build_test_example for gcc
+build_test_example-u-18_04:
+  extends: .build_test_example
+  image: corsika/devel:u-18.04
+  dependencies:
+    - config-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# build_test_example for clang
+build_test_example-clang-8:
+  extends: .build_test_example
+  image: corsika/devel:clang-8
+  dependencies:
+    - config-clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+####### INSTALL  ##############
+
+##########################################################
+# generic install template job
+# make install
+.install:
+  stage: install
+  tags:
+    - corsika
+  script:
+    - cd build
+    - set -o pipefail
+    - make -j2 install
+  rules:
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: manual
+      allow_failure: true
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+      allow_failure: true
+    - if: $CI_COMMIT_TAG
+      when: manual
+      allow_failure: true
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull
+
+# install for gcc
+install-u-18_04:
+  extends: .install
+  image: corsika/devel:u-18.04
+  dependencies:
+    - build-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# install for clang
+install-clang-8:
+  extends: .install
+  image: corsika/devel:clang-8
+  dependencies:
+    - build-clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+####### OPTIONAL ##############
+
+##########################################################
+# generic release template job
+# optional release pipeline for each commit
+.release:
+  stage: optional
+  tags:
+    - corsika
+  script:
+    - cd build
+    - cmake .. -DCMAKE_BUILD_TYPE=Release -DUSE_Pythia8_C8=C8
+    - cmake --build . -- -j4
+    - set -o pipefail
+    - ctest -j4
+    - make install
+    - mkdir -p build_examples
+    - cd build_examples
+    - cmake ../install/share/corsika/examples
+    - make -j4
+    - make -j4 run_examples | gzip -v -9 > examples.log.gz
+  rules:
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: manual
+      allow_failure: true
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+      allow_failure: true
+    - if: $CI_COMMIT_TAG
+      when: manual
+      allow_failure: true
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull
+  artifacts:
+    when: always
+    expire_in: 3 days
+    reports:
+      junit:
+        - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+    paths:
+      - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz
+      - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
+
+# release for gcc
+release-full-u-18_04:
+  extends: .release
+  image: corsika/devel:u-18.04
+  dependencies:
+    - config-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+# release for clang
+release-full-clang-8:
+  extends: .release
+  image: corsika/devel:clang-8
+  dependencies:
+    - config-clang-8
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang"
+
+
+
+
+##########################################################
+# the coverage generation should either run when manually requested, OR always on the master
+
+coverage:
+  image: corsika/devel:u-18.04
+  dependencies:
+    - config-u-18_04
+  stage: optional
+  tags:
+    - corsika
+  script:
+    - cd build
+    - cmake .. -DCMAKE_BUILD_TYPE=Coverage -DUSE_Pythia8_C8=C8
+    - cmake --build . -- -j4
+    - ctest -j4 
+    - cmake --build . --target coverage
+    - tar czf coverage-report.tar.gz coverage-report
+  coverage: '/^.*lines\.+:\s(.*\%)\s/'
+  rules:
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+      allow_failure: true
+    - if: $CI_COMMIT_TAG
+      when: manual
+      allow_failure: true
+  artifacts:
+    when: always
+    expire_in: 1 year
+    paths:
+      - ${CI_PROJECT_DIR}/build/coverage-report.tar.gz
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+
+
+
+
+
+
+##########################################################
+sanity:
+  image: corsika/devel:u-18.04
+  dependencies:
+    - config-u-18_04
+  stage: optional
+  tags:
+    - corsika
+  script:
+    - cd build
+    - cmake .. -DWITH_CORSIKA_SANITIZERS_ENABLED=ON
+    - cmake --build . -- -j4
+  rules:
+    - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+      when: manual
+      allow_failure: true
+    - if: $CI_MERGE_REQUEST_ID
+      when: manual
+      allow_failure: true
+    - if: $CI_COMMIT_TAG
+      when: manual
+      allow_failure: true
+  cache:
+    paths:
+      - ${CI_PROJECT_DIR}/build/
+    untracked: true
+    policy: pull
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+
+##########################################################
+# template for all Python jobs
+.python:
+  stage: python
+  tags:
+    - corsika
+  before_script:
+    - apt-get update && apt-get install -y python3-pip
+    - pip3 install --upgrade cython
+  script:
+    - cd ${CI_PROJECT_DIR}/python  # change into the Python directory
+    - pip3 install --user -e '.[test]'  # install the package + test deps
+    - python3 --version
+    - python3 -m mypy corsika
+    - python3 -m isort --atomic --check-only corsika tests
+    - python3 -m black -t py37 corsika tests
+    - python3 -m flake8 corsika tests
+    - python3 -m pytest --cov=corsika tests
+    - cd ${CI_PROJECT_DIR}  # reset the directory
+  coverage: '/^TOTAL\s*\d+\s*\d+\s*(.*\%)/'
+
+# the default Python version Ubuntu 18.04 is Python3.8
+python-3.8:
+  extends: .python
+  image: corsika/devel:u-18.04
+  dependencies:
+    - build-u-18_04
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-gcc"
+  artifacts:
+    when: always
+    expire_in: 1 year
+    paths:
+      - ${CI_PROJECT_DIR}/Python/python-test.log
+  allow_failure: true
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e1d017f06c817abfb615ed93d85af1d4a1f47da8..6d2e374011722c63f467780839821c6a473c0fb6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -109,7 +109,6 @@ message (STATUS "Installing dependencies from Conan (this may take some time)...
 conan_cmake_run (CONANFILE conanfile.txt
                  BASIC_SETUP CMAKE_TARGETS
                  BUILD missing
-                 BUILD bison
                  BUILD_TYPE "Release"
                  SETTINGS compiler.libcxx=libstdc++11)
 #
diff --git a/corsika/detail/framework/utility/CorsikaData.inl b/corsika/detail/framework/utility/CorsikaData.inl
index fc638b81a7f2e1a7c66908835d06182d99e69610..c3a82ad11d59e6849617b521735d8b6a92212e1d 100644
--- a/corsika/detail/framework/utility/CorsikaData.inl
+++ b/corsika/detail/framework/utility/CorsikaData.inl
@@ -16,7 +16,7 @@
 inline boost::filesystem::path corsika::corsika_data(boost::filesystem::path const& key) {
   if (auto const* p = std::getenv("CORSIKA_DATA"); p != nullptr) {
     return boost::filesystem::path(p) / key;
-  } else {
+  } else { // LCOV_EXCL_START, this cannot be easily tested system-independently
     throw std::runtime_error("CORSIKA_DATA not set");
-  }
+  } // LCOV_EXCL_STOP
 }
diff --git a/corsika/detail/media/BaseExponential.inl b/corsika/detail/media/BaseExponential.inl
index a40981bef9feb867c082e0d43ddb791352d8768b..3d7790c29bf80479a275e8a7349953d31c65c557 100644
--- a/corsika/detail/media/BaseExponential.inl
+++ b/corsika/detail/media/BaseExponential.inl
@@ -21,7 +21,7 @@ namespace corsika {
 
   template <typename TDerived>
   inline GrammageType BaseExponential<TDerived>::getIntegratedGrammage(
-      setup::Trajectory const& traj, LengthType vL, DirectionVector const& axis) const {
+      BaseTrajectory const& traj, LengthType vL, DirectionVector const& axis) const {
     if (vL == LengthType::zero()) { return GrammageType::zero(); }
 
     auto const uDotA = traj.getDirection(0).dot(axis).magnitude();
@@ -36,7 +36,7 @@ namespace corsika {
 
   template <typename TDerived>
   inline LengthType BaseExponential<TDerived>::getArclengthFromGrammage(
-      setup::Trajectory const& traj, GrammageType grammage,
+      BaseTrajectory const& traj, GrammageType grammage,
       DirectionVector const& axis) const {
     auto const uDotA = traj.getDirection(0).dot(axis).magnitude();
     auto const rhoStart = getImplementation().getMassDensity(traj.getPosition(0));
diff --git a/corsika/detail/media/FlatExponential.inl b/corsika/detail/media/FlatExponential.inl
index cdada02df557031db3c80ce91eb1de75eca1415e..b64b2ded2f3546886f490fcfc9e3a37906609d01 100644
--- a/corsika/detail/media/FlatExponential.inl
+++ b/corsika/detail/media/FlatExponential.inl
@@ -40,13 +40,13 @@ namespace corsika {
 
   template <typename T>
   inline GrammageType FlatExponential<T>::getIntegratedGrammage(
-      setup::Trajectory const& line, LengthType to) const {
+      BaseTrajectory const& line, LengthType to) const {
     return BaseExponential<FlatExponential<T>>::getIntegratedGrammage(line, to, axis_);
   }
 
   template <typename T>
   inline LengthType FlatExponential<T>::getArclengthFromGrammage(
-      setup::Trajectory const& line, GrammageType grammage) const {
+      BaseTrajectory const& line, GrammageType grammage) const {
     return BaseExponential<FlatExponential<T>>::getArclengthFromGrammage(line, grammage,
                                                                          axis_);
   }
diff --git a/corsika/detail/media/HomogeneousMedium.inl b/corsika/detail/media/HomogeneousMedium.inl
index fd0148b0278dcbad64f5ae37ca4a1d9b01243ba6..368073c9f61f2bed7f05f907128940cb16ef1d7f 100644
--- a/corsika/detail/media/HomogeneousMedium.inl
+++ b/corsika/detail/media/HomogeneousMedium.inl
@@ -32,14 +32,14 @@ namespace corsika {
   }
 
   template <typename T>
-  inline GrammageType HomogeneousMedium<T>::getIntegratedGrammage(
-      setup::Trajectory const&, LengthType to) const {
+  inline GrammageType HomogeneousMedium<T>::getIntegratedGrammage(BaseTrajectory const&,
+                                                                  LengthType to) const {
     return to * density_;
   }
 
   template <typename T>
   inline LengthType HomogeneousMedium<T>::getArclengthFromGrammage(
-      setup::Trajectory const&, GrammageType grammage) const {
+      BaseTrajectory const&, GrammageType grammage) const {
     return grammage / density_;
   }
 } // namespace corsika
diff --git a/corsika/detail/media/InhomogeneousMedium.inl b/corsika/detail/media/InhomogeneousMedium.inl
index 48d1d266e5f308cddb6b990678aa6b66ace2e473..8706765addf54494958788dd5709a7bc2ab9094b 100644
--- a/corsika/detail/media/InhomogeneousMedium.inl
+++ b/corsika/detail/media/InhomogeneousMedium.inl
@@ -36,13 +36,13 @@ namespace corsika {
 
   template <typename T, typename TDensityFunction>
   inline GrammageType InhomogeneousMedium<T, TDensityFunction>::getIntegratedGrammage(
-      setup::Trajectory const& line, LengthType to) const {
+      BaseTrajectory const& line, LengthType to) const {
     return densityFunction_.getIntegrateGrammage(line, to);
   }
 
   template <typename T, typename TDensityFunction>
   inline LengthType InhomogeneousMedium<T, TDensityFunction>::getArclengthFromGrammage(
-      setup::Trajectory const& line, GrammageType grammage) const {
+      BaseTrajectory const& line, GrammageType grammage) const {
     return densityFunction_.getArclengthFromGrammage(line, grammage);
   }
 
diff --git a/corsika/detail/media/LinearApproximationIntegrator.inl b/corsika/detail/media/LinearApproximationIntegrator.inl
index 78b7733ed138f12ace9e99ed381776487094cadc..ced2dee38b7f477c7c81d9bc80c1fe27024094b6 100644
--- a/corsika/detail/media/LinearApproximationIntegrator.inl
+++ b/corsika/detail/media/LinearApproximationIntegrator.inl
@@ -19,7 +19,7 @@ namespace corsika {
 
   template <typename TDerived>
   inline auto LinearApproximationIntegrator<TDerived>::getIntegrateGrammage(
-      setup::Trajectory const& line, LengthType length) const {
+      BaseTrajectory const& line, LengthType length) const {
     auto const c0 = getImplementation().evaluateAt(line.getPosition(0));
     auto const c1 = getImplementation().rho_.getFirstDerivative(line.getPosition(0),
                                                                 line.getDirection(0));
@@ -28,7 +28,7 @@ namespace corsika {
 
   template <typename TDerived>
   inline auto LinearApproximationIntegrator<TDerived>::getArclengthFromGrammage(
-      setup::Trajectory const& line, GrammageType grammage) const {
+      BaseTrajectory const& line, GrammageType grammage) const {
     auto const c0 = getImplementation().rho_(line.getPosition(0));
     auto const c1 = getImplementation().rho_.getFirstDerivative(line.getPosition(0),
                                                                 line.getDirection(0));
@@ -38,7 +38,7 @@ namespace corsika {
 
   template <typename TDerived>
   inline auto LinearApproximationIntegrator<TDerived>::getMaximumLength(
-      setup::Trajectory const& line, [[maybe_unused]] double relError) const {
+      BaseTrajectory const& line, [[maybe_unused]] double relError) const {
     [[maybe_unused]] auto const c1 = getImplementation().rho_.getSecondDerivative(
         line.getPosition(0), line.getDirection(0));
 
diff --git a/corsika/detail/media/SlidingPlanarExponential.inl b/corsika/detail/media/SlidingPlanarExponential.inl
index 56d99404ce835b860e93ba8db21ec2ce34462d4b..61911772a432e5d4d1c5b6cec3fc13f9ea2c8cdf 100644
--- a/corsika/detail/media/SlidingPlanarExponential.inl
+++ b/corsika/detail/media/SlidingPlanarExponential.inl
@@ -39,7 +39,7 @@ namespace corsika {
 
   template <typename T>
   inline GrammageType SlidingPlanarExponential<T>::getIntegratedGrammage(
-      setup::Trajectory const& traj, LengthType l) const {
+      BaseTrajectory const& traj, LengthType l) const {
     auto const axis = (traj.getPosition(0) -
                        BaseExponential<SlidingPlanarExponential<T>>::getAnchorPoint())
                           .normalized();
@@ -49,7 +49,7 @@ namespace corsika {
 
   template <typename T>
   inline LengthType SlidingPlanarExponential<T>::getArclengthFromGrammage(
-      setup::Trajectory const& traj, GrammageType const grammage) const {
+      BaseTrajectory const& traj, GrammageType const grammage) const {
     auto const axis = (traj.getPosition(0) -
                        BaseExponential<SlidingPlanarExponential<T>>::getAnchorPoint())
                           .normalized();
diff --git a/corsika/detail/modules/HadronicElasticModel.inl b/corsika/detail/modules/HadronicElasticModel.inl
index e048a2d9179fb4cd195af22bafa5e51f403ded65..a8ecf017ddb89c7c261dd03a09c544208e66ecb9 100644
--- a/corsika/detail/modules/HadronicElasticModel.inl
+++ b/corsika/detail/modules/HadronicElasticModel.inl
@@ -16,8 +16,6 @@
 #include <corsika/framework/random/ExponentialDistribution.hpp>
 #include <corsika/framework/utility/COMBoost.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-
 #include <iomanip>
 #include <iostream>
 
@@ -28,9 +26,9 @@ namespace corsika {
       : parX_(x)
       , parY_(y) {}
 
-  template <>
+  template <typename TParticle>
   inline GrammageType HadronicElasticInteraction::getInteractionLength(
-      SetupParticle const& p) {
+      TParticle const& p) {
     if (p.getPID() == Code::Proton) {
       auto const* currentNode = p.getNode();
       auto const& mediumComposition =
diff --git a/corsika/detail/modules/LongitudinalProfile.inl b/corsika/detail/modules/LongitudinalProfile.inl
index 643ea017313a7358fdfd22a7e02643975bcebfec..6987e3d80f0367cc45a57b8f1b04cf332652e97d 100644
--- a/corsika/detail/modules/LongitudinalProfile.inl
+++ b/corsika/detail/modules/LongitudinalProfile.inl
@@ -11,9 +11,6 @@
 
 #include <corsika/modules/LongitudinalProfile.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <cmath>
 #include <iomanip>
 #include <limits>
diff --git a/corsika/detail/modules/ObservationPlane.inl b/corsika/detail/modules/ObservationPlane.inl
index 12614573f13888396cc28e3655b9119fa7af2bec..967b9314198af14e7eb76b9e99d654d60c00c573 100644
--- a/corsika/detail/modules/ObservationPlane.inl
+++ b/corsika/detail/modules/ObservationPlane.inl
@@ -6,15 +6,12 @@
  * the license.
  */
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 namespace corsika {
 
-  template <typename TOutput>
-  ObservationPlane<TOutput>::ObservationPlane(Plane const& obsPlane,
-                                              DirectionVector const& x_axis,
-                                              bool deleteOnHit)
+  template <typename TTracking, typename TOutput>
+  ObservationPlane<TTracking, TOutput>::ObservationPlane(Plane const& obsPlane,
+                                                         DirectionVector const& x_axis,
+                                                         bool deleteOnHit)
       : plane_(obsPlane)
       , deleteOnHit_(deleteOnHit)
       , energy_ground_(0_GeV)
@@ -22,10 +19,10 @@ namespace corsika {
       , xAxis_(x_axis.normalized())
       , yAxis_(obsPlane.getNormal().cross(xAxis_)) {}
 
-  template <typename TOutput>
-  inline ProcessReturn ObservationPlane<TOutput>::doContinuous(
-      corsika::setup::Stack::particle_type& particle, corsika::setup::Trajectory&,
-      bool const stepLimit) {
+  template <typename TTracking, typename TOutput>
+  template <typename TParticle, typename TTrajectory>
+  inline ProcessReturn ObservationPlane<TTracking, TOutput>::doContinuous(
+      TParticle& particle, TTrajectory&, bool const stepLimit) {
     /*
        The current step did not yet reach the ObservationPlane, do nothing now and wait:
      */
@@ -61,17 +58,16 @@ namespace corsika {
     }
   }
 
-  template <typename TOutput>
-  inline LengthType ObservationPlane<TOutput>::getMaxStepLength(
-      corsika::setup::Stack::particle_type const& particle,
-      corsika::setup::Trajectory const& trajectory) {
+  template <typename TTracking, typename TOutput>
+  template <typename TParticle, typename TTrajectory>
+  inline LengthType ObservationPlane<TTracking, TOutput>::getMaxStepLength(
+      TParticle const& particle, TTrajectory const& trajectory) {
 
     CORSIKA_LOG_TRACE("particle={}, pos={}, dir={}, plane={}", particle.asString(),
                       particle.getPosition(), particle.getDirection(), plane_.asString());
 
-    Intersections const intersection =
-        setup::Tracking::intersect<corsika::setup::Stack::particle_type>(particle,
-                                                                         plane_);
+    auto const intersection = TTracking::intersect(particle, plane_);
+
     TimeType const timeOfIntersection = intersection.getEntry();
     CORSIKA_LOG_TRACE("timeOfIntersection={}", timeOfIntersection);
     if (timeOfIntersection < TimeType::zero()) {
@@ -87,8 +83,8 @@ namespace corsika {
     return dist;
   }
 
-  template <typename TOutput>
-  inline void ObservationPlane<TOutput>::showResults() const {
+  template <typename TTracking, typename TOutput>
+  inline void ObservationPlane<TTracking, TOutput>::showResults() const {
     CORSIKA_LOG_INFO(
         " ******************************\n"
         " ObservationPlane: \n"
@@ -98,8 +94,8 @@ namespace corsika {
         energy_ground_ / 1_GeV, count_ground_);
   }
 
-  template <typename TOutput>
-  inline YAML::Node ObservationPlane<TOutput>::getConfig() const {
+  template <typename TTracking, typename TOutput>
+  inline YAML::Node ObservationPlane<TTracking, TOutput>::getConfig() const {
     using namespace units::si;
 
     // construct the top-level node
@@ -141,8 +137,8 @@ namespace corsika {
     return node;
   }
 
-  template <typename TOutput>
-  inline void ObservationPlane<TOutput>::reset() {
+  template <typename TTracking, typename TOutput>
+  inline void ObservationPlane<TTracking, TOutput>::reset() {
     energy_ground_ = 0_GeV;
     count_ground_ = 0;
   }
diff --git a/corsika/detail/modules/ParticleCut.inl b/corsika/detail/modules/ParticleCut.inl
index 04aff2d69f0e237541c73389a10b5fc7823004b0..a5c6dceda32463b237c6e2995c2c9151fe2b8ff0 100644
--- a/corsika/detail/modules/ParticleCut.inl
+++ b/corsika/detail/modules/ParticleCut.inl
@@ -8,7 +8,7 @@
 
 #pragma once
 
-#include <corsika/modules/ParticleCut.hpp>
+#include <corsika/framework/core/Logging.hpp>
 
 namespace corsika {
 
@@ -25,7 +25,7 @@ namespace corsika {
       , em_count_(0)
       , inv_count_(0) {
     for (auto p : get_all_particles())
-      if (is_hadron(p))
+      if (is_hadron(p)) // nuclei are also hadrons
         set_kinetic_energy_threshold(p, eHadCut);
       else if (is_muon(p))
         set_kinetic_energy_threshold(p, eMuCut);
@@ -33,9 +33,6 @@ namespace corsika {
         set_kinetic_energy_threshold(p, eEleCut);
       else if (p == Code::Photon)
         set_kinetic_energy_threshold(p, ePhoCut);
-      else if (p == Code::Nucleus)
-        // nuclei have same threshold as hadrons on the nucleon level.
-        set_kinetic_energy_threshold(p, eHadCut);
     CORSIKA_LOG_DEBUG(
         "setting kinetic energy thresholds: electrons = {} GeV, photons = {} GeV, "
         "hadrons = {} GeV, "
@@ -148,7 +145,8 @@ namespace corsika {
     return false; // this particle will not be removed/cut
   }
 
-  inline void ParticleCut::doSecondaries(corsika::setup::StackView& vS) {
+  template <typename TStackView>
+  inline void ParticleCut::doSecondaries(TStackView& vS) {
     auto particle = vS.begin();
     while (particle != vS.end()) {
       if (checkCutParticle(particle)) { particle.erase(); }
@@ -156,9 +154,9 @@ namespace corsika {
     }
   }
 
-  inline ProcessReturn ParticleCut::doContinuous(
-      corsika::setup::Stack::particle_type& particle, corsika::setup::Trajectory const&,
-      bool const) {
+  template <typename TParticle, typename TTrajectory>
+  inline ProcessReturn ParticleCut::doContinuous(TParticle& particle, TTrajectory const&,
+                                                 bool const) {
     CORSIKA_LOG_TRACE("ParticleCut::DoContinuous");
     if (checkCutParticle(particle)) {
       CORSIKA_LOG_TRACE("removing during continuous");
diff --git a/corsika/detail/modules/StackInspector.inl b/corsika/detail/modules/StackInspector.inl
index 2d9c8238a4a5bb74b6ae80d6b218df0f1ef31221..e339b2e89858a233112b9c6a5f126d6d5ab5851f 100644
--- a/corsika/detail/modules/StackInspector.inl
+++ b/corsika/detail/modules/StackInspector.inl
@@ -14,8 +14,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/RootCoordinateSystem.hpp>
 
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <chrono>
 #include <iomanip>
 #include <iostream>
diff --git a/corsika/detail/modules/conex/CONEXhybrid.inl b/corsika/detail/modules/conex/CONEXhybrid.inl
index 6d94ab74759f09168d765948324ce5830aafe7de..6fbfb02e18acba5dddf6a5108bdfd91126a802fc 100644
--- a/corsika/detail/modules/conex/CONEXhybrid.inl
+++ b/corsika/detail/modules/conex/CONEXhybrid.inl
@@ -125,7 +125,8 @@ namespace corsika {
     ::conex::conexrun_(ipart, eprima, theta, phi, xminp, dimpact, ioseed.data());
   }
 
-  inline void CONEXhybrid::doSecondaries(setup::StackView& vS) {
+  template <typename TStackView>
+  inline void CONEXhybrid::doSecondaries(TStackView& vS) {
     auto p = vS.begin();
     while (p != vS.end()) {
       Code const pid = p.getPID();
diff --git a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
index 31c34ce7b970dab276dad90a942a718422588ea1..a2f643cc58d5f72f77e64579157e9a351aa411ed 100644
--- a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
+++ b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
@@ -10,9 +10,6 @@
 
 #include <corsika/framework/core/ParticleProperties.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/core/Logging.hpp>
 
@@ -32,7 +29,8 @@ namespace corsika {
       , shower_axis_(shower_axis)
       , profile_(int(shower_axis.getMaximumX() / dX_) + 1) {}
 
-  inline HEPEnergyType BetheBlochPDG::getBetheBloch(setup::Stack::particle_type const& p,
+  template <typename TParticle>
+  inline HEPEnergyType BetheBlochPDG::getBetheBloch(TParticle const& p,
                                                     GrammageType const dX) {
 
     // all these are material constants and have to come through Environment
@@ -126,21 +124,23 @@ namespace corsika {
   }
 
   // radiation losses according to PDG 2018, ch. 33 ref. [5]
-  inline HEPEnergyType BetheBlochPDG::getRadiationLosses(
-      setup::Stack::particle_type const& vP, GrammageType const vDX) {
+  template <typename TParticle>
+  inline HEPEnergyType BetheBlochPDG::getRadiationLosses(TParticle const& vP,
+                                                         GrammageType const vDX) {
     // simple-minded hard-coded value for b(E) inspired by data from
     // http://pdg.lbl.gov/2018/AtomicNuclearProperties/ for N and O.
     auto constexpr b = 3.0 * 1e-6 * square(1_cm) / 1_g;
     return -vP.getEnergy() * b * vDX;
   }
 
-  inline HEPEnergyType BetheBlochPDG::getTotalEnergyLoss(
-      setup::Stack::particle_type const& vP, GrammageType const vDX) {
+  template <typename TParticle>
+  inline HEPEnergyType BetheBlochPDG::getTotalEnergyLoss(TParticle const& vP,
+                                                         GrammageType const vDX) {
     return getBetheBloch(vP, vDX) + getRadiationLosses(vP, vDX);
   }
 
-  inline ProcessReturn BetheBlochPDG::doContinuous(setup::Stack::particle_type& p,
-                                                   setup::Trajectory const& t,
+  template <typename TParticle, typename TTrajectory>
+  inline ProcessReturn BetheBlochPDG::doContinuous(TParticle& p, TTrajectory const& t,
                                                    bool const) {
 
     // if this step was limiting the CORSIKA stepping, the particle is lost
@@ -170,9 +170,9 @@ namespace corsika {
     return ProcessReturn::Ok;
   }
 
-  inline LengthType BetheBlochPDG::getMaxStepLength(
-      setup::Stack::particle_type const& vParticle,
-      setup::Trajectory const& vTrack) const {
+  template <typename TParticle, typename TTrajectory>
+  inline LengthType BetheBlochPDG::getMaxStepLength(TParticle const& vParticle,
+                                                    TTrajectory const& vTrack) const {
     if (vParticle.getChargeNumber() == 0) {
       return meter * std::numeric_limits<double>::infinity();
     }
@@ -195,14 +195,15 @@ namespace corsika {
         vTrack, maxGrammage);
   }
 
-  inline void BetheBlochPDG::updateMomentum(corsika::setup::Stack::particle_type& vP,
-                                            HEPEnergyType Enew) {
+  template <typename TParticle>
+  inline void BetheBlochPDG::updateMomentum(TParticle& vP, HEPEnergyType Enew) {
     HEPMomentumType Pnew = elab2plab(Enew, vP.getMass());
     auto pnew = vP.getMomentum();
     vP.setMomentum(pnew * Pnew / pnew.getNorm());
   }
 
-  inline void BetheBlochPDG::fillProfile(setup::Trajectory const& vTrack,
+  template <typename TTrajectory>
+  inline void BetheBlochPDG::fillProfile(TTrajectory const& vTrack,
                                          const HEPEnergyType dE) {
 
     GrammageType grammageStart = shower_axis_.getProjectedX(vTrack.getPosition(0));
diff --git a/corsika/detail/modules/proposal/ContinuousProcess.inl b/corsika/detail/modules/proposal/ContinuousProcess.inl
index b8403dc145a85f212ceb95699443e56916c7336a..e65046c2324d4cb57119d22b4bd44953aec4a95e 100644
--- a/corsika/detail/modules/proposal/ContinuousProcess.inl
+++ b/corsika/detail/modules/proposal/ContinuousProcess.inl
@@ -16,12 +16,6 @@
 #include <corsika/framework/utility/COMBoost.hpp>
 #include <corsika/framework/core/Logging.hpp>
 
-#include <corsika/setup/SetupEnvironment.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
-#include <iostream>
-
 namespace corsika::proposal {
 
   inline void ContinuousProcess::buildCalculator(Code code,
@@ -54,13 +48,12 @@ namespace corsika::proposal {
     calc[std::make_pair(comp.getHash(), code)] = std::move(calculator);
   }
 
-  template <>
-  inline ContinuousProcess::ContinuousProcess(setup::Environment const& _env)
+  template <typename TEnvironment>
+  inline ContinuousProcess::ContinuousProcess(TEnvironment const& _env)
       : ProposalProcessBase(_env) {}
 
-  template <>
-  inline void ContinuousProcess::scatter(setup::Stack::particle_type& vP,
-                                         HEPEnergyType const& loss,
+  template <typename TParticle>
+  inline void ContinuousProcess::scatter(TParticle& vP, HEPEnergyType const& loss,
                                          GrammageType const& grammage) {
 
     // get or build corresponding calculators
@@ -91,9 +84,9 @@ namespace corsika::proposal {
     vP.setMomentum(MomentumVector(vP_dir.getCoordinateSystem(), vec));
   }
 
-  template <>
-  inline ProcessReturn ContinuousProcess::doContinuous(setup::Stack::particle_type& vP,
-                                                       setup::Trajectory const& vT,
+  template <typename TParticle, typename TTrajectory>
+  inline ProcessReturn ContinuousProcess::doContinuous(TParticle& vP,
+                                                       TTrajectory const& vT,
                                                        bool const) {
 
     if (!canInteract(vP.getPID())) return ProcessReturn::Ok;
@@ -119,9 +112,9 @@ namespace corsika::proposal {
     return ProcessReturn::Ok;
   }
 
-  template <>
-  inline LengthType ContinuousProcess::getMaxStepLength(
-      setup::Stack::particle_type const& vP, setup::Trajectory const& vT) {
+  template <typename TParticle, typename TTrajectory>
+  inline LengthType ContinuousProcess::getMaxStepLength(TParticle const& vP,
+                                                        TTrajectory const& vT) {
     auto const code = vP.getPID();
     if (!canInteract(code)) return meter * std::numeric_limits<double>::infinity();
 
diff --git a/corsika/detail/modules/proposal/Interaction.inl b/corsika/detail/modules/proposal/Interaction.inl
index 019d31270abeb1da6fe2a99121081363762dcac6..c450594146f13a7afaa809c99d0f3354d9427b02 100644
--- a/corsika/detail/modules/proposal/Interaction.inl
+++ b/corsika/detail/modules/proposal/Interaction.inl
@@ -12,10 +12,6 @@
 #include <corsika/framework/utility/COMBoost.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
 
-#include <corsika/setup/SetupEnvironment.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <limits>
 #include <memory>
 #include <random>
@@ -23,8 +19,8 @@
 
 namespace corsika::proposal {
 
-  template <>
-  inline Interaction::Interaction(setup::Environment const& _env)
+  template <typename TEnvironment>
+  inline Interaction::Interaction(TEnvironment const& _env)
       : ProposalProcessBase(_env) {}
 
   inline void Interaction::buildCalculator(Code code, NuclearComposition const& comp) {
@@ -52,8 +48,8 @@ namespace corsika::proposal {
         PROPOSAL::make_interaction(c, true));
   }
 
-  template <>
-  inline ProcessReturn Interaction::doInteraction(setup::StackView& view) {
+  template <typename TStackView>
+  inline ProcessReturn Interaction::doInteraction(TStackView& view) {
 
     auto const projectile = view.getProjectile();
 
@@ -106,9 +102,8 @@ namespace corsika::proposal {
     return ProcessReturn::Ok;
   }
 
-  template <>
-  inline GrammageType Interaction::getInteractionLength(
-      setup::Stack::particle_type const& projectile) {
+  template <typename TParticle>
+  inline GrammageType Interaction::getInteractionLength(TParticle const& projectile) {
 
     if (canInteract(projectile.getPID())) {
       auto c = getCalculator(projectile, calc);
diff --git a/corsika/detail/modules/proposal/ProposalProcessBase.inl b/corsika/detail/modules/proposal/ProposalProcessBase.inl
index 7b07100888bbdb8c7ecd63fdbbc57bdc7668dd4b..e423bd626b75400e8300146267ea02720adda469 100644
--- a/corsika/detail/modules/proposal/ProposalProcessBase.inl
+++ b/corsika/detail/modules/proposal/ProposalProcessBase.inl
@@ -12,10 +12,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/utility/COMBoost.hpp>
 
-#include <corsika/setup/SetupEnvironment.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <cstdlib>
 #include <iostream>
 #include <limits>
@@ -30,7 +26,8 @@ namespace corsika::proposal {
     return false;
   }
 
-  inline ProposalProcessBase::ProposalProcessBase(setup::Environment const& _env)
+  template <typename TEnvironment>
+  inline ProposalProcessBase::ProposalProcessBase(TEnvironment const& _env)
       : RNG_(RNGManager::getInstance().getRandomStream("proposal")) {
     _env.getUniverse()->walk([&](auto& vtn) {
       if (vtn.hasModelProperties()) {
diff --git a/corsika/detail/modules/pythia8/Decay.inl b/corsika/detail/modules/pythia8/Decay.inl
index 163ec37a7864fdd2b2c85022aaafce73e73d6739..f3f743364bd37b2bca2c65d3418f59c3cad25bf0 100644
--- a/corsika/detail/modules/pythia8/Decay.inl
+++ b/corsika/detail/modules/pythia8/Decay.inl
@@ -12,9 +12,6 @@
 
 #include <corsika/framework/utility/COMBoost.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 namespace corsika::pythia8 {
 
   inline Decay::Decay(bool const print_listing)
diff --git a/corsika/detail/modules/pythia8/Interaction.inl b/corsika/detail/modules/pythia8/Interaction.inl
index 74a910de769ff259aeafacc46e9b2d74d02e32e5..9e13f4de82834de41ad14f411583169ec811a79f 100644
--- a/corsika/detail/modules/pythia8/Interaction.inl
+++ b/corsika/detail/modules/pythia8/Interaction.inl
@@ -15,8 +15,6 @@
 #include <corsika/media/Environment.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-
 #include <tuple>
 
 namespace corsika::pythia8 {
@@ -155,14 +153,14 @@ namespace corsika::pythia8 {
     }
   }
 
-  inline GrammageType Interaction::getInteractionLength(
-      corsika::setup::Stack::particle_type const& particle) {
+  template <typename TParticle>
+  inline GrammageType Interaction::getInteractionLength(TParticle const& particle) {
 
     // coordinate system, get global frame of reference
     MomentumVector const& pMomentum = particle.getMomentum();
     CoordinateSystemPtr const& labCS = pMomentum.getCoordinateSystem();
 
-    Code const corsikaBeamId = particle.getPID();
+    Code corsikaBeamId = particle.getPID();
 
     // beam particles for pythia : 1, 2, 3 for p, pi, k
     // read from cross section code table
diff --git a/corsika/detail/modules/qgsjetII/Interaction.inl b/corsika/detail/modules/qgsjetII/Interaction.inl
index f442b239cdefab4f237594d3659d30e91f362d1c..1ee0f978d772159780968bab6625729606acfec0 100644
--- a/corsika/detail/modules/qgsjetII/Interaction.inl
+++ b/corsika/detail/modules/qgsjetII/Interaction.inl
@@ -15,8 +15,6 @@
 #include <corsika/modules/qgsjetII/ParticleConversion.hpp>
 #include <corsika/modules/qgsjetII/QGSJetIIFragmentsStack.hpp>
 #include <corsika/modules/qgsjetII/QGSJetIIStack.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
 #include <corsika/framework/utility/COMBoost.hpp>
 
 #include <sstream>
diff --git a/corsika/detail/modules/sibyll/Decay.inl b/corsika/detail/modules/sibyll/Decay.inl
index 1474df5c33272409608134549b3c1ad7a981c9b2..55f8446ffa86ad491fbf2508b1a3fd907939b399 100644
--- a/corsika/detail/modules/sibyll/Decay.inl
+++ b/corsika/detail/modules/sibyll/Decay.inl
@@ -12,16 +12,9 @@
 #include <corsika/modules/sibyll/ParticleConversion.hpp>
 #include <corsika/modules/sibyll/SibStack.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <iostream>
 #include <vector>
 
-using SetupView = corsika::setup::StackView;
-using SetupProjectile = corsika::setup::StackView::particle_type;
-using SetupParticle = corsika::setup::Stack::particle_type;
-
 namespace corsika::sibyll {
 
   inline Decay::Decay(const bool sibyll_printout_on)
diff --git a/corsika/detail/modules/sibyll/Interaction.inl b/corsika/detail/modules/sibyll/Interaction.inl
index 39d240df8c513ec007302ff146bef00bd56cfff7..c6adf5e105c3ca496658d6ea0c71a43a7a18e481 100644
--- a/corsika/detail/modules/sibyll/Interaction.inl
+++ b/corsika/detail/modules/sibyll/Interaction.inl
@@ -15,19 +15,12 @@
 #include <corsika/framework/geometry/FourVector.hpp>
 #include <corsika/modules/sibyll/ParticleConversion.hpp>
 #include <corsika/modules/sibyll/SibStack.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
 #include <corsika/framework/utility/COMBoost.hpp>
 
 #include <sibyll2.3d.hpp>
 
 #include <tuple>
 
-using namespace corsika;
-using SetupParticle = setup::Stack::stack_iterator_type;
-using SetupView = setup::StackView;
-using Track = setup::Trajectory;
-
 namespace corsika::sibyll {
 
   inline Interaction::Interaction(const bool sibyll_printout_on)
@@ -84,9 +77,9 @@ namespace corsika::sibyll {
     return std::make_tuple(sigProd * 1_mb, sigEla * 1_mb);
   }
 
-  template <>
+  template <typename TParticle>
   inline corsika::GrammageType Interaction::getInteractionLength(
-      SetupParticle const& projectile) const {
+      TParticle const& projectile) const {
 
     const corsika::Code corsikaBeamId = projectile.getPID();
 
diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl
index e806626865655616b9e00e9d4183dbd54c322a30..4b40f19f40da8eba02ba6dac1735d913c641cd91 100644
--- a/corsika/detail/modules/urqmd/UrQMD.inl
+++ b/corsika/detail/modules/urqmd/UrQMD.inl
@@ -158,8 +158,7 @@ namespace corsika::urqmd {
     }
   }
 
-  template <typename TParticle> // need template here, as this is called both with
-                                // SetupParticle as well as SetupProjectile
+  template <typename TParticle>
   inline CrossSectionType UrQMD::getCrossSection(TParticle const& projectile,
                                                  Code targetCode) const {
     auto const projectileCode = projectile.getPID();
diff --git a/corsika/detail/stack/history/HistoryObservationPlane.inl b/corsika/detail/stack/history/HistoryObservationPlane.inl
index 66b0f11e450e83b422ce4d0f4d6a5340e86ebf9f..6cd65c060190f7bae58771d82465193a34828c2d 100644
--- a/corsika/detail/stack/history/HistoryObservationPlane.inl
+++ b/corsika/detail/stack/history/HistoryObservationPlane.inl
@@ -13,15 +13,18 @@
 
 namespace corsika::history {
 
-  inline HistoryObservationPlane::HistoryObservationPlane(setup::Stack const& stack,
-                                                          Plane const& obsPlane,
-                                                          bool deleteOnHit)
+  template <typename TStack>
+  inline HistoryObservationPlane<TStack> HistoryObservationPlane(TStack const& stack,
+                                                                 Plane const& obsPlane,
+                                                                 bool deleteOnHit)
       : stack_{stack}
       , plane_{obsPlane}
       , deleteOnHit_{deleteOnHit} {}
 
-  inline ProcessReturn HistoryObservationPlane::DoContinuous(
-      setup::Stack::ParticleType const& particle, setup::Trajectory const& trajectory) {
+  template <typename TStack>
+  template <typename TParticle, typename TTrajectory>
+  inline ProcessReturn HistoryObservationPlane<TStack> DoContinuous(
+      TParticle const& particle, TTrajectory const& trajectory) {
     TimeType const timeOfIntersection =
         (plane_.getCenter() - trajectory.getR0()).dot(plane_.getNormal()) /
         trajectory.getV0().dot(plane_.getNormal());
@@ -45,8 +48,10 @@ namespace corsika::history {
     }
   }
 
-  inline LengthType HistoryObservationPlane::MaxStepLength(
-      setup::Stack::ParticleType const&, setup::Trajectory const& trajectory) {
+  template <typename TStack>
+  template <typename TParticle, typename TTrajectory>
+  inline LengthType HistoryObservationPlane<TStack> MaxStepLength(
+      TParticle const&, TTrajectory const& trajectory) {
     TimeType const timeOfIntersection =
         (plane_.getCenter() - trajectory.getR0()).dot(plane_.getNormal()) /
         trajectory.getV0().dot(plane_.getNormal());
@@ -59,8 +64,10 @@ namespace corsika::history {
     return (trajectory.getR0() - pointOfIntersection).norm() * 1.0001;
   }
 
-  inline void HistoryObservationPlane::fillHistoryHistogram(
-      setup::Stack::ParticleType const& muon) {
+  template <typename TStack>
+  template <typename TParticle>
+  inline void HistoryObservationPlane<TStack> fillHistoryHistogram(
+      TParticle const& muon) {
     double const muon_energy = muon.getEnergy() / 1_GeV;
 
     int genctr{0};
diff --git a/corsika/framework/geometry/BaseTrajectory.hpp b/corsika/framework/geometry/BaseTrajectory.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..01177b8923b45e5da37c72c10c091efd16140aa3
--- /dev/null
+++ b/corsika/framework/geometry/BaseTrajectory.hpp
@@ -0,0 +1,54 @@
+/*
+ * (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/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Point.hpp>
+
+namespace corsika {
+
+  /**
+   *
+   * A Trajectory is a description of a momvement of an object in
+   * three-dimensional space that describes the trajectory (connection
+   * between two Points in space), as well as the direction of motion
+   * at any given point.
+   *
+   * A Trajectory has a start `0` and an end `1`, where
+   * e.g. getPosition(0) returns the start point and getDirection(1)
+   * the direction of motion at the end. Values outside 0...1 are not
+   * defined.
+   *
+   * A Trajectory has a length in [m], getLength, a duration in [s], getDuration.
+   *
+   * Note: so far it is assumed that the speed (d|vec{r}|/dt) between
+   * start and end does not change and is constant for the entire
+   * Trajectory.
+   *
+   **/
+
+  class BaseTrajectory {
+
+  public:
+    virtual Point getPosition(double const u) const = 0;
+
+    virtual VelocityVector getVelocity(double const u) const = 0;
+
+    virtual DirectionVector getDirection(double const u) const = 0;
+
+    virtual TimeType getDuration(double const u = 1) const = 0;
+
+    virtual LengthType getLength(double const u = 1) const = 0;
+
+    virtual void setLength(LengthType const limit) = 0;
+
+    virtual void setDuration(TimeType const limit) = 0;
+  };
+
+} // namespace corsika
diff --git a/corsika/framework/geometry/LeapFrogTrajectory.hpp b/corsika/framework/geometry/LeapFrogTrajectory.hpp
index 11e3534a19ae6c8ead194077ae515afb194bbd4c..ec281e5f71d56c77602b2dd64b22d5e3e47ea8c9 100644
--- a/corsika/framework/geometry/LeapFrogTrajectory.hpp
+++ b/corsika/framework/geometry/LeapFrogTrajectory.hpp
@@ -12,6 +12,7 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
@@ -29,7 +30,7 @@ namespace corsika {
    *
    **/
 
-  class LeapFrogTrajectory {
+  class LeapFrogTrajectory : public BaseTrajectory {
 
   public:
     LeapFrogTrajectory() = delete;
diff --git a/corsika/framework/geometry/StraightTrajectory.hpp b/corsika/framework/geometry/StraightTrajectory.hpp
index b05746269b017f7a80fe5c5807ca93c7afe336ab..4bed6b7f6ead06789a5712b11148f509b7eb5e9c 100644
--- a/corsika/framework/geometry/StraightTrajectory.hpp
+++ b/corsika/framework/geometry/StraightTrajectory.hpp
@@ -9,25 +9,14 @@
 #pragma once
 
 #include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/framework/geometry/Line.hpp>
-#include <corsika/framework/geometry/Point.hpp>
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
   /**
    *
-   * A Trajectory is a description of a momvement of an object in
-   * three-dimensional space that describes the trajectory (connection
-   * between two Points in space), as well as the direction of motion
-   * at any given point.
-   *
-   * A Trajectory has a start `0` and an end `1`, where
-   * e.g. getPosition(0) returns the start point and getDirection(1)
-   * the direction of motion at the end. Values outside 0...1 are not
-   * defined.
-   *
-   * A Trajectory has a length in [m], getLength, a duration in [s], getDuration.
+   * This implements a straight trajectory between two points.
    *
    * Note: so far it is assumed that the speed (d|vec{r}|/dt) between
    * start and end does not change and is constant for the entire
@@ -35,7 +24,7 @@ namespace corsika {
    *
    **/
 
-  class StraightTrajectory {
+  class StraightTrajectory : public BaseTrajectory {
 
   public:
     StraightTrajectory() = delete;
diff --git a/corsika/media/BaseExponential.hpp b/corsika/media/BaseExponential.hpp
index c992d7807d9f94a1a64f9a68d880ba35092b9f03..66f907943386d6f0d56f24577d39c5671cb00ab0 100644
--- a/corsika/media/BaseExponential.hpp
+++ b/corsika/media/BaseExponential.hpp
@@ -12,7 +12,7 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 #include <limits>
 
 namespace corsika {
@@ -40,7 +40,7 @@ namespace corsika {
      * \f]
      */
     // clang-format on
-    GrammageType getIntegratedGrammage(setup::Trajectory const& line, LengthType vL,
+    GrammageType getIntegratedGrammage(BaseTrajectory const& line, LengthType vL,
                                        DirectionVector const& axis) const;
 
     // clang-format off
@@ -61,8 +61,7 @@ namespace corsika {
      * \f]
      */
     // clang-format on
-    LengthType getArclengthFromGrammage(setup::Trajectory const& line,
-                                        GrammageType grammage,
+    LengthType getArclengthFromGrammage(BaseTrajectory const& line, GrammageType grammage,
                                         DirectionVector const& axis) const;
 
   public:
diff --git a/corsika/media/FlatExponential.hpp b/corsika/media/FlatExponential.hpp
index a33c76496fb89d80b9a0e7a5747b6578bb5c9698..71767d705a030db91179ef70bb3441cfaf5c9d2d 100644
--- a/corsika/media/FlatExponential.hpp
+++ b/corsika/media/FlatExponential.hpp
@@ -13,7 +13,7 @@
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/media/BaseExponential.hpp>
 #include <corsika/media/NuclearComposition.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
@@ -41,10 +41,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(setup::Trajectory const& line,
+    GrammageType getIntegratedGrammage(BaseTrajectory const& line,
                                        LengthType to) const override;
 
-    LengthType getArclengthFromGrammage(setup::Trajectory const& line,
+    LengthType getArclengthFromGrammage(BaseTrajectory const& line,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/media/HomogeneousMedium.hpp b/corsika/media/HomogeneousMedium.hpp
index a659be2288aef59492c9ab84922e6495c01a46d6..95a9fb0ae91ee41aa8b8f6036afbda3f105c8207 100644
--- a/corsika/media/HomogeneousMedium.hpp
+++ b/corsika/media/HomogeneousMedium.hpp
@@ -12,8 +12,7 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/media/NuclearComposition.hpp>
-
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 /**
  * a homogeneous medium
@@ -31,10 +30,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(setup::Trajectory const&,
+    GrammageType getIntegratedGrammage(BaseTrajectory const&,
                                        LengthType to) const override;
 
-    LengthType getArclengthFromGrammage(setup::Trajectory const&,
+    LengthType getArclengthFromGrammage(BaseTrajectory const&,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/media/IEmpty.hpp b/corsika/media/IEmpty.hpp
index 1912f90fc5e09ce403ef9c4e33c163e63f96309f..e67771d24f2c28445dab05416412575cdab3bb61 100644
--- a/corsika/media/IEmpty.hpp
+++ b/corsika/media/IEmpty.hpp
@@ -9,7 +9,7 @@
 #pragma once
 
 #include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
@@ -25,7 +25,7 @@ namespace corsika {
 
   class IEmpty {
   public:
-    virtual LengthType getArclengthFromGrammage(setup::Trajectory const&,
+    virtual LengthType getArclengthFromGrammage(BaseTrajectory const&,
                                                 GrammageType) const = 0;
 
     virtual ~IEmpty() {}
@@ -34,7 +34,7 @@ namespace corsika {
   template <typename TModel = IEmpty>
   class Empty : public TModel {
   public:
-    LengthType getArclengthFromGrammage(setup::Trajectory const&, GrammageType) const {
+    LengthType getArclengthFromGrammage(BaseTrajectory const&, GrammageType) const {
       return 0. * meter;
     }
   };
diff --git a/corsika/media/IMediumModel.hpp b/corsika/media/IMediumModel.hpp
index 2dcacffbbf1cbfc80e634eea8a171e95b5bf31c6..87e702c5dba8cf7aafdbc0ac3fe0f5f9ba090e75 100644
--- a/corsika/media/IMediumModel.hpp
+++ b/corsika/media/IMediumModel.hpp
@@ -11,7 +11,7 @@
 #include <corsika/media/NuclearComposition.hpp>
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
@@ -23,10 +23,10 @@ namespace corsika {
 
     // todo: think about the mixin inheritance of the trajectory vs the BaseTrajectory
     // approach; for now, only lines are supported
-    virtual GrammageType getIntegratedGrammage(setup::Trajectory const&,
+    virtual GrammageType getIntegratedGrammage(BaseTrajectory const&,
                                                LengthType) const = 0;
 
-    virtual LengthType getArclengthFromGrammage(setup::Trajectory const&,
+    virtual LengthType getArclengthFromGrammage(BaseTrajectory const&,
                                                 GrammageType) const = 0;
 
     virtual NuclearComposition const& getNuclearComposition() const = 0;
diff --git a/corsika/media/InhomogeneousMedium.hpp b/corsika/media/InhomogeneousMedium.hpp
index 7c9f664aaf589a311de7207d79a214125c8cdd15..a257bc508969c4620172b4dae45e0cf52fa06ae3 100644
--- a/corsika/media/InhomogeneousMedium.hpp
+++ b/corsika/media/InhomogeneousMedium.hpp
@@ -12,7 +12,7 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/media/NuclearComposition.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 /**
  * A general inhomogeneous medium. The mass density distribution TDensityFunction must be
@@ -32,10 +32,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(setup::Trajectory const& line,
+    GrammageType getIntegratedGrammage(BaseTrajectory const& line,
                                        LengthType to) const override;
 
-    LengthType getArclengthFromGrammage(setup::Trajectory const& pLine,
+    LengthType getArclengthFromGrammage(BaseTrajectory const& pLine,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/media/LinearApproximationIntegrator.hpp b/corsika/media/LinearApproximationIntegrator.hpp
index dfd9f0037476ea9d4e8dc8ed5ae3be6acccf8943..0e2eb0a295cc373c5749acb444b13a206c05622d 100644
--- a/corsika/media/LinearApproximationIntegrator.hpp
+++ b/corsika/media/LinearApproximationIntegrator.hpp
@@ -11,7 +11,7 @@
 #include <limits>
 
 #include <corsika/framework/geometry/Line.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
@@ -20,12 +20,12 @@ namespace corsika {
     auto const& getImplementation() const;
 
   public:
-    auto getIntegrateGrammage(setup::Trajectory const& line, LengthType length) const;
+    auto getIntegrateGrammage(BaseTrajectory const& line, LengthType length) const;
 
-    auto getArclengthFromGrammage(setup::Trajectory const& line,
+    auto getArclengthFromGrammage(BaseTrajectory const& line,
                                   GrammageType grammage) const;
 
-    auto getMaximumLength(setup::Trajectory const& line,
+    auto getMaximumLength(BaseTrajectory const& line,
                           [[maybe_unused]] double relError) const;
   };
 
diff --git a/corsika/media/SlidingPlanarExponential.hpp b/corsika/media/SlidingPlanarExponential.hpp
index 9060dc8765abc90e05147503462f7a9b1de5afe9..a06c293a4d1056e74b40f52bb7fac7f3cf3a0070 100644
--- a/corsika/media/SlidingPlanarExponential.hpp
+++ b/corsika/media/SlidingPlanarExponential.hpp
@@ -15,7 +15,7 @@
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/media/FlatExponential.hpp>
 #include <corsika/media/NuclearComposition.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/framework/geometry/BaseTrajectory.hpp>
 
 namespace corsika {
 
@@ -46,10 +46,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(setup::Trajectory const& line,
+    GrammageType getIntegratedGrammage(BaseTrajectory const& line,
                                        LengthType l) const override;
 
-    LengthType getArclengthFromGrammage(setup::Trajectory const& line,
+    LengthType getArclengthFromGrammage(BaseTrajectory const& line,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/modules/ObservationPlane.hpp b/corsika/modules/ObservationPlane.hpp
index 613708d1090b19f6cb314f09f62e837ee2e55e20..6d71d8f7611e98b7b38044782538a06c3cb69a64 100644
--- a/corsika/modules/ObservationPlane.hpp
+++ b/corsika/modules/ObservationPlane.hpp
@@ -11,8 +11,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Plane.hpp>
 #include <corsika/framework/process/ContinuousProcess.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
 #include <corsika/modules/writers/ObservationPlaneWriterParquet.hpp>
 
 namespace corsika {
@@ -32,19 +30,20 @@ namespace corsika {
      small gap in between the two plane in such a scenario, or develop
      another more specialized output class.
    */
-  template <typename TOutputWriter = ObservationPlaneWriterParquet>
-  class ObservationPlane : public ContinuousProcess<ObservationPlane<TOutputWriter>>,
-                           public TOutputWriter {
+  template <typename TTracking, typename TOutputWriter = ObservationPlaneWriterParquet>
+  class ObservationPlane
+      : public ContinuousProcess<ObservationPlane<TTracking, TOutputWriter>>,
+        public TOutputWriter {
 
   public:
     ObservationPlane(Plane const&, DirectionVector const&, bool = true);
 
-    ProcessReturn doContinuous(corsika::setup::Stack::particle_type& vParticle,
-                               corsika::setup::Trajectory& vTrajectory,
+    template <typename TParticle, typename TTrajectory>
+    ProcessReturn doContinuous(TParticle& vParticle, TTrajectory& vTrajectory,
                                bool const stepLimit);
 
-    LengthType getMaxStepLength(corsika::setup::Stack::particle_type const&,
-                                corsika::setup::Trajectory const& vTrajectory);
+    template <typename TParticle, typename TTrajectory>
+    LengthType getMaxStepLength(TParticle const&, TTrajectory const& vTrajectory);
 
     void showResults() const;
     void reset();
diff --git a/corsika/modules/OnShellCheck.hpp b/corsika/modules/OnShellCheck.hpp
index b51f55adac5b8b85aeafdde2db711e26c06ab61d..e572a58820b34f8fc96bd69293bdcaab134f8b0f 100644
--- a/corsika/modules/OnShellCheck.hpp
+++ b/corsika/modules/OnShellCheck.hpp
@@ -10,7 +10,6 @@
 
 #include <corsika/framework/core/ParticleProperties.hpp>
 #include <corsika/framework/process/SecondariesProcess.hpp>
-#include <corsika/setup/SetupStack.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
 
 namespace corsika {
diff --git a/corsika/modules/ParticleCut.hpp b/corsika/modules/ParticleCut.hpp
index 7857c691f53024263ab2dd8e284cab8777a9e317..e9df7a602ec974963e8e3bc2ef566c56f19a9020 100644
--- a/corsika/modules/ParticleCut.hpp
+++ b/corsika/modules/ParticleCut.hpp
@@ -15,9 +15,6 @@
 #include <corsika/framework/process/SecondariesProcess.hpp>
 #include <corsika/framework/process/ContinuousProcess.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 namespace corsika {
   /**
      simple ParticleCut process. Goes through the secondaries of an interaction and
@@ -52,13 +49,16 @@ namespace corsika {
     ParticleCut(std::unordered_map<Code const, HEPEnergyType const> const& eCuts,
                 bool const em, bool const inv);
 
-    void doSecondaries(corsika::setup::StackView&);
+    template <typename TStackView>
+    void doSecondaries(TStackView&);
+
+    template <typename TParticle, typename TTrajectory>
     ProcessReturn doContinuous(
-        corsika::setup::Stack::particle_type& vParticle,
-        corsika::setup::Trajectory const& vTrajectory,
+        TParticle& vParticle, TTrajectory const& vTrajectory,
         const bool limitFlag = false); // this is not used for ParticleCut
-    LengthType getMaxStepLength(corsika::setup::Stack::particle_type const&,
-                                corsika::setup::Trajectory const&) {
+
+    template <typename TParticle, typename TTrajectory>
+    LengthType getMaxStepLength(TParticle const&, TTrajectory const&) {
       return meter * std::numeric_limits<double>::infinity();
     }
 
diff --git a/corsika/modules/conex/CONEXhybrid.hpp b/corsika/modules/conex/CONEXhybrid.hpp
index 659cbe95bda2b0635b15f909e0c3708195946c07..2f0b80873dfa4109202f1a01cb11ff571c988cfd 100644
--- a/corsika/modules/conex/CONEXhybrid.hpp
+++ b/corsika/modules/conex/CONEXhybrid.hpp
@@ -14,7 +14,6 @@
 #include <corsika/framework/geometry/Vector.hpp>
 #include <corsika/framework/process/SecondariesProcess.hpp>
 #include <corsika/media/ShowerAxis.hpp>
-#include <corsika/setup/SetupStack.hpp>
 
 #include <corsika/modules/conex/CONEX_f.hpp>
 
@@ -29,7 +28,9 @@ namespace corsika {
   public:
     CONEXhybrid(Point center, ShowerAxis const& showerAxis, LengthType groundDist,
                 LengthType injectionHeight, HEPEnergyType primaryEnergy, PDGCode pdg);
-    void doSecondaries(setup::StackView&);
+
+    template <typename TStackView>
+    void doSecondaries(TStackView&);
 
     void solveCE();
 
diff --git a/corsika/modules/energy_loss/BetheBlochPDG.hpp b/corsika/modules/energy_loss/BetheBlochPDG.hpp
index 582a0c4e03046cc738105d32d2cfcdd7e94964a9..5f606b82db38b532e52c24c2399911b194f658d9 100644
--- a/corsika/modules/energy_loss/BetheBlochPDG.hpp
+++ b/corsika/modules/energy_loss/BetheBlochPDG.hpp
@@ -14,9 +14,6 @@
 #include <corsika/framework/process/ContinuousProcess.hpp>
 #include <corsika/media/ShowerAxis.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
 #include <map>
 
 namespace corsika {
@@ -52,18 +49,23 @@ namespace corsika {
      * \param limitFlag flag to identify, if BetheBlochPDG::getMaxStepLength is the
      *        globally limiting factor (or not)
      clang-format-on **/
-    ProcessReturn doContinuous(setup::Stack::particle_type& particle,
-                               setup::Trajectory const& track, bool const limitFlag);
-    LengthType getMaxStepLength(setup::Stack::particle_type const&,
-                                setup::Trajectory const&)
+    template <typename TParticle, typename TTrajectory>
+    ProcessReturn doContinuous(TParticle& particle, TTrajectory const& track,
+                               bool const limitFlag);
+
+    template <typename TParticle, typename TTrajectory>
+    LengthType getMaxStepLength(TParticle const&,
+                                TTrajectory const&)
         const; //! limited by the energy threshold! By default the limit is the particle
                //! rest mass, i.e. kinetic energy is zero
-    static HEPEnergyType getBetheBloch(setup::Stack::particle_type const&,
-                                       const GrammageType);
-    static HEPEnergyType getRadiationLosses(setup::Stack::particle_type const&,
-                                            const GrammageType);
-    static HEPEnergyType getTotalEnergyLoss(setup::Stack::particle_type const&,
-                                            const GrammageType);
+    template <typename TParticle>
+    static HEPEnergyType getBetheBloch(TParticle const&, const GrammageType);
+
+    template <typename TParticle>
+    static HEPEnergyType getRadiationLosses(TParticle const&, const GrammageType);
+
+    template <typename TParticle>
+    static HEPEnergyType getTotalEnergyLoss(TParticle const&, const GrammageType);
 
     void showResults() const;
     void reset();
@@ -72,8 +74,11 @@ namespace corsika {
     HEPEnergyType getTotal() const;
 
   private:
-    void updateMomentum(corsika::setup::Stack::particle_type&, HEPEnergyType Enew);
-    void fillProfile(setup::Trajectory const&, HEPEnergyType);
+    template <typename TParticle>
+    void updateMomentum(TParticle&, HEPEnergyType Enew);
+
+    template <typename TTrajectory>
+    void fillProfile(TTrajectory const&, HEPEnergyType);
 
     GrammageType const dX_ = 10_g / square(1_cm); // profile binning
     GrammageType const dX_threshold_ = 0.0001_g / square(1_cm);
diff --git a/corsika/modules/proposal/Interaction.hpp b/corsika/modules/proposal/Interaction.hpp
index 80a8a7864247e2bc798db5564aa8506869ce1525..b6f2fabbab60f772f69eb83bd9f78697a08fb8f8 100644
--- a/corsika/modules/proposal/Interaction.hpp
+++ b/corsika/modules/proposal/Interaction.hpp
@@ -16,12 +16,8 @@
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/random/UniformRealDistribution.hpp>
 
-#include <corsika/media/Environment.hpp>
-
 #include <corsika/modules/proposal/ProposalProcessBase.hpp>
 
-#include <array>
-
 namespace corsika::proposal {
 
   //!
diff --git a/corsika/modules/proposal/ProposalProcessBase.hpp b/corsika/modules/proposal/ProposalProcessBase.hpp
index 3edb90720f2346161f062c45ec79b8f9de85c639..d1231a5db0923db85f51fdd1400ca8923c6d003e 100644
--- a/corsika/modules/proposal/ProposalProcessBase.hpp
+++ b/corsika/modules/proposal/ProposalProcessBase.hpp
@@ -13,8 +13,6 @@
 #include <corsika/framework/core/ParticleProperties.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
 
-#include <corsika/setup/SetupEnvironment.hpp>
-
 #include <array>
 
 namespace corsika::proposal {
@@ -83,7 +81,8 @@ namespace corsika::proposal {
     //! Store cut and  nuclear composition of the whole universe in media which are
     //! required for creating crosssections by proposal.
     //!
-    ProposalProcessBase(corsika::setup::Environment const& _env);
+    template <typename TEnvironment>
+    ProposalProcessBase(TEnvironment const& _env);
 
     //!
     //! Checks if a particle can be processed by proposal
diff --git a/corsika/modules/pythia8/Interaction.hpp b/corsika/modules/pythia8/Interaction.hpp
index f2d264db15befbad789b0ba2afd122fa0e586f86..f1a6e0d935c63367472025e10444aad4820eabc0 100644
--- a/corsika/modules/pythia8/Interaction.hpp
+++ b/corsika/modules/pythia8/Interaction.hpp
@@ -14,8 +14,6 @@
 #include <corsika/framework/process/InteractionProcess.hpp>
 #include <corsika/modules/pythia8/Pythia8.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-
 #include <tuple>
 
 namespace corsika::pythia8 {
@@ -38,7 +36,8 @@ namespace corsika::pythia8 {
     std::tuple<CrossSectionType, CrossSectionType> getCrossSection(
         const Code BeamId, const Code TargetId, const HEPEnergyType CoMenergy);
 
-    GrammageType getInteractionLength(corsika::setup::Stack::particle_type const&);
+    template <typename TParticle>
+    GrammageType getInteractionLength(TParticle const&);
 
     /**
        In this function PYTHIA is called to produce one event. The
diff --git a/corsika/modules/urqmd/UrQMD.hpp b/corsika/modules/urqmd/UrQMD.hpp
index 69cb4a93750b3389749c279cdc0b6f8f84765ad5..ca857e170ac5007367b16f9f526cd0e719434ddb 100644
--- a/corsika/modules/urqmd/UrQMD.hpp
+++ b/corsika/modules/urqmd/UrQMD.hpp
@@ -14,8 +14,6 @@
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/utility/CorsikaData.hpp>
 
-#include <corsika/setup/SetupStack.hpp>
-
 #include <boost/filesystem/path.hpp>
 #include <boost/multi_array.hpp>
 
diff --git a/corsika/stack/history/HistoryObservationPlane.hpp b/corsika/stack/history/HistoryObservationPlane.hpp
index 9c97f8722218f24449ef32538be81ea2ac5c1056..11a76dc071703981d08af4cf84dcc48ef5751876 100644
--- a/corsika/stack/history/HistoryObservationPlane.hpp
+++ b/corsika/stack/history/HistoryObservationPlane.hpp
@@ -10,8 +10,6 @@
 
 #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>
@@ -24,22 +22,26 @@
 
 namespace corsika::history {
 
-  class HistoryObservationPlane : public ContinuousProcess<HistoryObservationPlane> {
+  template <typename TStack>
+  class HistoryObservationPlane
+      : public ContinuousProcess<HistoryObservationPlane<TStack>> {
   public:
-    HistoryObservationPlane(setup::Stack const&, Plane const&, bool = true);
+    HistoryObservationPlane(TStack const&, Plane const&, bool = true);
 
-    LengthType getMaxStepLength(setup::Stack::particle_type const&,
-                                setup::Trajectory const& vTrajectory);
+    template <typename TParticle, typename TTrajectory>
+    LengthType getMaxStepLength(TParticle const&, TTrajectory const& vTrajectory);
 
-    ProcessReturn doContinuous(setup::Stack::particle_type const& vParticle,
-                               setup::Trajectory const& vTrajectory);
+    template <typename TParticle, typename TTrajectory>
+    ProcessReturn doContinuous(TParticle const& vParticle,
+                               TTrajectory const& vTrajectory);
 
     auto const& histogram() const { return histogram_; }
 
   private:
-    void fillHistoryHistogram(setup::Stack::particle_type const&);
+    template <typename TParticle>
+    void fillHistoryHistogram(TParticle const&);
 
-    setup::Stack const& stack_;
+    TStack const& stack_;
     Plane const plane_;
     bool const deleteOnHit_;
 
diff --git a/examples/corsika.cpp b/examples/corsika.cpp
index 9bb4ee59244dd87a1b364876218c753ddef31064..863e27a98876a9751a076be1934e3e1e60bcac93 100644
--- a/examples/corsika.cpp
+++ b/examples/corsika.cpp
@@ -28,6 +28,7 @@
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 #include <corsika/output/OutputManager.hpp>
+#include <corsika/output/NoOutput.hpp>
 
 #include <corsika/media/Environment.hpp>
 #include <corsika/media/FlatExponential.hpp>
@@ -313,8 +314,10 @@ int main(int argc, char** argv) {
 
   // observation plane
   Plane const obsPlane(showerCore, DirectionVector(rootCS, {0., 0., 1.}));
-  ObservationPlane observationLevel(obsPlane, DirectionVector(rootCS, {1., 0., 0.}));
-  output.add("obslevel", observationLevel);
+  ObservationPlane<setup::Tracking, NoOutput> observationLevel(
+      obsPlane, DirectionVector(rootCS, {1., 0., 0.}));
+  // register the observation plane with the output
+  output.add("particles", observationLevel);
 
   // assemble the final process sequence
   auto sequence =
diff --git a/examples/em_shower.cpp b/examples/em_shower.cpp
index 9e8d2a39e6740bf8e6403f75be20b9ff80b9741b..378d8b3ee5a3749f2952d12e5d320f7a33eeb226 100644
--- a/examples/em_shower.cpp
+++ b/examples/em_shower.cpp
@@ -156,8 +156,8 @@ int main(int argc, char** argv) {
   LongitudinalProfile longprof{showerAxis};
 
   Plane const obsPlane(showerCore, DirectionVector(rootCS, {0., 0., 1.}));
-  ObservationPlane observationLevel(obsPlane, DirectionVector(rootCS, {1., 0., 0.}),
-                                    "particles.dat");
+  ObservationPlane<setup::Tracking> observationLevel(
+      obsPlane, DirectionVector(rootCS, {1., 0., 0.}), "particles.dat");
   output.add("obsplane", observationLevel);
 
   auto sequence = make_sequence(emCascadeCounted, emContinuous, longprof, cut,
diff --git a/examples/hybrid_MC.cpp b/examples/hybrid_MC.cpp
index dc4f7e8ee784fa1e5188785a38fa3b6dbc3d4be1..9993d390e25ccecf752827b49d758a7555e67e72 100644
--- a/examples/hybrid_MC.cpp
+++ b/examples/hybrid_MC.cpp
@@ -220,8 +220,8 @@ int main(int argc, char** argv) {
   LongitudinalProfile longprof{showerAxis};
 
   Plane const obsPlane(showerCore, DirectionVector(rootCS, {0., 0., 1.}));
-  ObservationPlane observationLevel(obsPlane, DirectionVector(rootCS, {1., 0., 0.}),
-                                    "particles.dat");
+  ObservationPlane<setup::Tracking> observationLevel(
+      obsPlane, DirectionVector(rootCS, {1., 0., 0.}), "particles.dat");
   output.add("obsplane", observationLevel);
 
   corsika::urqmd::UrQMD urqmd_model;
diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp
index b8e16ab432e45620a2007888c20a4a633848e95f..1904927228ab38e94c6a5ae9d8ccaf54699cddfa 100644
--- a/examples/vertical_EAS.cpp
+++ b/examples/vertical_EAS.cpp
@@ -28,6 +28,7 @@
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 #include <corsika/output/OutputManager.hpp>
+#include <corsika/output/NoOutput.hpp>
 
 #include <corsika/media/Environment.hpp>
 #include <corsika/media/FlatExponential.hpp>
@@ -285,7 +286,8 @@ int main(int argc, char** argv) {
   LongitudinalProfile longprof{showerAxis};
 
   Plane const obsPlane(showerCore, DirectionVector(rootCS, {0., 0., 1.}));
-  ObservationPlane observationLevel(obsPlane, DirectionVector(rootCS, {1., 0., 0.}));
+  ObservationPlane<setup::Tracking, NoOutput> observationLevel(
+      obsPlane, DirectionVector(rootCS, {1., 0., 0.}));
   // register the observation plane with the output
   output.add("particles", observationLevel);
 
diff --git a/modules/qgsjetII/qgsjet-II-04.cpp b/modules/qgsjetII/qgsjet-II-04.cpp
index 8ffd24df4bd7b860116b7792e16671562af051b6..550091632723bb6127ce8c3e20145b4868ed40a1 100644
--- a/modules/qgsjetII/qgsjet-II-04.cpp
+++ b/modules/qgsjetII/qgsjet-II-04.cpp
@@ -3,10 +3,10 @@
 #include <iostream>
 
 datadir::datadir(std::string const& dir) {
-  if (dir.length() > 130) {
+  if (dir.length() > 130) { // LCOV_EXCL_START since we can't test this error message
     std::cerr << "QGSJetII error, will cut datadir \"" << dir
               << "\" to 130 characters: " << std::endl;
-  }
+  } // LCOV_EXCL_STOP
   int i = 0;
   for (i = 0; i < std::min(130, int(dir.length())); ++i) data[i] = dir[i];
   data[i + 0] = ' ';
diff --git a/tests/framework/testCascade.cpp b/tests/framework/testCascade.cpp
index 1d5cb6be27ba186e7ebaccc7872d2dbbabfc9cd8..df55ee318d51675bb4cb1f19b0da38d25d7316d3 100644
--- a/tests/framework/testCascade.cpp
+++ b/tests/framework/testCascade.cpp
@@ -27,6 +27,7 @@
 #include <corsika/output/DummyOutputManager.hpp>
 
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <catch2/catch.hpp>
 
diff --git a/tests/media/testEnvironment.cpp b/tests/media/testEnvironment.cpp
index 9a5cfab0e81d99f816abfc7da29c31e4e8ebe378..b39fbb357757c28d51b6ff13b0a0f7dfceba480f 100644
--- a/tests/media/testEnvironment.cpp
+++ b/tests/media/testEnvironment.cpp
@@ -29,6 +29,7 @@
 #include <corsika/media/VolumeTreeNode.hpp>
 
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <catch2/catch.hpp>
 
diff --git a/tests/media/testMagneticField.cpp b/tests/media/testMagneticField.cpp
index 73827dd92fbfafe5e0a697b3ce2e6f13757af5c6..b3ccf4d75c4ef8f98aff716b2be559775c3a0a3f 100644
--- a/tests/media/testMagneticField.cpp
+++ b/tests/media/testMagneticField.cpp
@@ -16,6 +16,7 @@
 #include <corsika/media/VolumeTreeNode.hpp>
 
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <catch2/catch.hpp>
 
diff --git a/tests/media/testMedium.cpp b/tests/media/testMedium.cpp
index 709185d18524b7d5536657ffbd005bbafde9cfb9..a10348717921ded38235b69a81328c694b0b87fe 100644
--- a/tests/media/testMedium.cpp
+++ b/tests/media/testMedium.cpp
@@ -21,6 +21,7 @@
 #include <catch2/catch.hpp>
 
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 using namespace corsika;
 
diff --git a/tests/media/testRefractiveIndex.cpp b/tests/media/testRefractiveIndex.cpp
index 1aee5973e8dca12f3f45b743e47658ef324e5600..acef32c4f4fa5260ac841cf1db53a780f9f80900 100644
--- a/tests/media/testRefractiveIndex.cpp
+++ b/tests/media/testRefractiveIndex.cpp
@@ -19,6 +19,7 @@
 #include <corsika/media/ExponentialRefractiveIndex.hpp>
 
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <catch2/catch.hpp>
 
diff --git a/tests/modules/testObservationPlane.cpp b/tests/modules/testObservationPlane.cpp
index 62983a2e08f49eef546e017b104cb104ead3de41..1ed62d52453cf5c235d359e4958c2f2dbaa3a177 100644
--- a/tests/modules/testObservationPlane.cpp
+++ b/tests/modules/testObservationPlane.cpp
@@ -22,12 +22,13 @@
 #include <SetupTestEnvironment.hpp>
 #include <SetupTestStack.hpp>
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 using namespace corsika;
 
 TEST_CASE("ObservationPlane", "interface") {
 
-  logging::set_level(logging::level::trace);
+  logging::set_level(logging::level::info);
 
   auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen);
   auto const& cs = *csPtr;
@@ -47,7 +48,7 @@ TEST_CASE("ObservationPlane", "interface") {
 
   // dummy track. Not used for calculation!
   Point const start(cs, {0_m, 1_m, 10_m});
-  VelocityVector vec(cs, 0_m / second, 0_m / second, -constants::c);
+  VelocityVector vec(cs, constants::c, 0_m / second, 0_m / second);
   Line line(start, vec);
   setup::Trajectory no_used_track =
       setup::testing::make_track<setup::Trajectory>(line, 12_m / constants::c);
@@ -55,7 +56,8 @@ TEST_CASE("ObservationPlane", "interface") {
   SECTION("horizontal plane") {
 
     Plane const obsPlane(Point(cs, {10_m, 0_m, 0_m}), DirectionVector(cs, {1., 0., 0.}));
-    ObservationPlane<NoOutput> obs(obsPlane, DirectionVector(cs, {0., 1., 0.}));
+    ObservationPlane<setup::Tracking, NoOutput> obs(obsPlane,
+                                                    DirectionVector(cs, {0., 1., 0.}));
 
     LengthType const length = obs.getMaxStepLength(particle, no_used_track);
     ProcessReturn const ret = obs.doContinuous(particle, no_used_track, true);
@@ -73,7 +75,7 @@ TEST_CASE("ObservationPlane", "interface") {
 
     // particle past plane:
     {
-      particle.setPosition({cs, {0_m, 0_m, -1_m}});
+      particle.setPosition({cs, {11_m, 0_m, -1_m}});
       setup::Trajectory no_hit_track =
           setup::testing::make_track<setup::Trajectory>(line, 1_nm / constants::c);
       LengthType const no_hit = obs.getMaxStepLength(particle, no_hit_track);
@@ -83,13 +85,16 @@ TEST_CASE("ObservationPlane", "interface") {
 
   SECTION("transparent plane") {
     Plane const obsPlane(Point(cs, {1_m, 0_m, 0_m}), DirectionVector(cs, {1., 0., 0.}));
-    ObservationPlane<NoOutput> obs(obsPlane, DirectionVector(cs, {0., 0., 1.}), false);
+    ObservationPlane<setup::Tracking, NoOutput> obs(
+        obsPlane, DirectionVector(cs, {0., 0., 1.}), false);
 
     LengthType const length = obs.getMaxStepLength(particle, no_used_track);
     ProcessReturn const ret = obs.doContinuous(particle, no_used_track, false);
+    ProcessReturn const ret2 = obs.doContinuous(particle, no_used_track, true);
 
     CHECK(length / 1_m == Approx(1).margin(1e-4));
     CHECK(ret == ProcessReturn::Ok);
+    CHECK(ret2 == ProcessReturn::Ok);
   }
 
   SECTION("inclined plane, inclined particle") {
@@ -101,7 +106,8 @@ TEST_CASE("ObservationPlane", "interface") {
 
     Plane const obsPlane(Point(cs, {10_m, 5_m, 5_m}),
                          DirectionVector(cs, {1, 0.1, -0.05}));
-    ObservationPlane<NoOutput> obs(obsPlane, DirectionVector(cs, {0., 1., 0.}));
+    ObservationPlane<setup::Tracking, NoOutput> obs(obsPlane,
+                                                    DirectionVector(cs, {0., 1., 0.}));
 
     LengthType const length = obs.getMaxStepLength(particle, no_used_track);
     ProcessReturn const ret = obs.doContinuous(particle, no_used_track, true);
@@ -112,7 +118,8 @@ TEST_CASE("ObservationPlane", "interface") {
 
   SECTION("output") {
     Plane const obsPlane(Point(cs, {1_m, 0_m, 0_m}), DirectionVector(cs, {1., 0., 0.}));
-    ObservationPlane<NoOutput> obs(obsPlane, DirectionVector(cs, {0., 0., 1.}), false);
+    ObservationPlane<setup::Tracking, NoOutput> obs(
+        obsPlane, DirectionVector(cs, {0., 0., 1.}), false);
     auto const cfg = obs.getConfig();
     CHECK(cfg["type"]);
   }
diff --git a/tests/modules/testParticleCut.cpp b/tests/modules/testParticleCut.cpp
index 94ec3f27f194160547d35fbab2df13007b6c93b2..ea3a8bbe552d874ae5d26d9841caf7b556381e5c 100644
--- a/tests/modules/testParticleCut.cpp
+++ b/tests/modules/testParticleCut.cpp
@@ -18,6 +18,7 @@
 
 #include <SetupTestStack.hpp>
 #include <SetupTestTrajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <catch2/catch.hpp>
 
diff --git a/tests/modules/testPythia8.cpp b/tests/modules/testPythia8.cpp
index 09a7c067263bc4d30e6d477cd7f68e1231203ec1..d07c791f5a73cfeb8801597cc62d2c77091e727b 100644
--- a/tests/modules/testPythia8.cpp
+++ b/tests/modules/testPythia8.cpp
@@ -165,7 +165,7 @@ TEST_CASE("Pythia8Interface", "modules") {
         Code::Proton, 0, 0, 7_TeV, (setup::Environment::BaseNodeType* const)nodePtr,
         *csPtr);
     auto& view = *secViewPtr;
-    auto particle = stackPtr->first();
+    auto const particle = stackPtr->getNextParticle();
 
     corsika::pythia8::Interaction collision;
 
diff --git a/tests/modules/testStackInspector.cpp b/tests/modules/testStackInspector.cpp
index 9905583ed579e73cd059330805dd49f146e0c009..9464e54a55508bf5948ff4650a834f5a04bd34de 100644
--- a/tests/modules/testStackInspector.cpp
+++ b/tests/modules/testStackInspector.cpp
@@ -16,6 +16,7 @@
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 #include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <../framework/testCascade.hpp> //! \todo fix this
 
@@ -37,10 +38,14 @@ TEST_CASE("StackInspector", "modules") {
   stack.addParticle(std::make_tuple(Code::Electron,
                                     MomentumVector(rootCS, {0_GeV, 0_GeV, -1_GeV}),
                                     Point(rootCS, {0_m, 0_m, 10_km}), 0_ns));
+  stack.addParticle(std::make_tuple(Code::Nucleus,
+                                    MomentumVector(rootCS, {0_GeV, 0_GeV, -1_GeV}),
+                                    Point(rootCS, {0_m, 0_m, 10_km}), 0_ns, 16, 8));
 
   SECTION("interface") {
 
     StackInspector<TestCascadeStack> model(1, true, E0);
     model.doStack(stack);
+    // there are no actions, nothing to check...
   }
 }
diff --git a/tests/stack/testGeometryNodeStackExtension.cpp b/tests/stack/testGeometryNodeStackExtension.cpp
index e0673f3c33da61acedec35ddb0a1cae365d40044..9cc29edcce53750f3ab1b4c5ba4b5f2def5885ad 100644
--- a/tests/stack/testGeometryNodeStackExtension.cpp
+++ b/tests/stack/testGeometryNodeStackExtension.cpp
@@ -37,10 +37,9 @@ using StackWithGeometryInterface =
 using TestStack = CombinedStack<typename dummy_stack::DummyStack::stack_data_type,
                                 node::GeometryData<DummyEnv>, StackWithGeometryInterface>;
 
-TEST_CASE("GeometryNodeStackExtension", "[stack]") {
+TEST_CASE("GeometryNodeStackExtension", "stack") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   dummy_stack::NoData noData;
 
diff --git a/tests/stack/testHistoryStack.cpp b/tests/stack/testHistoryStack.cpp
index 485b794a531a25a0b0a55c7ec637a40432745872..4542db2ebf1dfe218b8566a04987696aff0b194a 100644
--- a/tests/stack/testHistoryStack.cpp
+++ b/tests/stack/testHistoryStack.cpp
@@ -46,10 +46,9 @@ using TestStack =
 
 using EvtPtr = std::shared_ptr<DummyEvent>;
 
-TEST_CASE("HistoryStackExtension", "[stack]") {
+TEST_CASE("HistoryStackExtension", "stack") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   [[maybe_unused]] CoordinateSystemPtr const& dummyCS = get_root_CoordinateSystem();
 
@@ -64,4 +63,14 @@ TEST_CASE("HistoryStackExtension", "[stack]") {
     EvtPtr evt = p.getEvent();
     CHECK(evt == nullptr);
   }
+
+  SECTION("add and remove particles") {
+
+    auto p = s.addParticle(std::tuple<dummy_stack::NoData>{noData});
+    CHECK(s.getEntries() == 2);
+    p.erase();
+    CHECK(s.getEntries() == 1);
+    s.purge();
+    CHECK(s.getEntries() == 1);
+  }
 }
diff --git a/tests/stack/testNuclearStackExtension.cpp b/tests/stack/testNuclearStackExtension.cpp
index fa9201023ee13e6d4fcc00460ecd67175f0b950d..198fe7da953b8f7567e733501280f89ed7f71d6e 100644
--- a/tests/stack/testNuclearStackExtension.cpp
+++ b/tests/stack/testNuclearStackExtension.cpp
@@ -227,6 +227,15 @@ TEST_CASE("NuclearStackExtension", "stack") {
                                          nuclear_stack::ExtendedParticleInterfaceType>
         s;
 
+    // not valid, no A,Z:
+    // not valid, no A,Z:
+    CHECK_THROWS(s.addParticle(
+        std::make_tuple(Code::Nucleus, 100_GeV, DirectionVector(dummyCS, {1, 0, 0}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s)));
+    CHECK_THROWS(s.addParticle(
+        std::make_tuple(Code::Nucleus, MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s)));
+
     // not valid:
     CHECK_THROWS(s.addParticle(std::make_tuple(
         Code::Oxygen, MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),