diff --git a/corsika/detail/media/LayeredSphericalAtmosphereBuilder.inl b/corsika/detail/media/LayeredSphericalAtmosphereBuilder.inl
index 791d57972d8496fb130bd87f1673a22686ff8557..17371ff3b775986099e6436894c1213a945e4733 100644
--- a/corsika/detail/media/LayeredSphericalAtmosphereBuilder.inl
+++ b/corsika/detail/media/LayeredSphericalAtmosphereBuilder.inl
@@ -13,7 +13,7 @@
 #include <corsika/media/FlatExponential.hpp>
 #include <corsika/media/HomogeneousMedium.hpp>
 #include <corsika/media/SlidingPlanarExponential.hpp>
-//#include <corsika/media/SlidingPlanarTabular.hpp>
+#include <corsika/media/SlidingPlanarTabular.hpp>
 
 namespace corsika {
 
@@ -42,7 +42,7 @@ namespace corsika {
       TModelArgs...>::addExponentialLayer(GrammageType b, LengthType c,
                                           LengthType upperBoundary) {
 
-    auto const radius = earthRadius_ + upperBoundary;
+    auto const radius = planetRadius_ + upperBoundary;
     checkRadius(radius);
     previousRadius_ = radius;
 
@@ -56,7 +56,7 @@ namespace corsika {
       auto lastBound = [&](auto... argPack) {
         return std::make_shared<
             TMediumModelExtra<SlidingPlanarExponential<TMediumInterface>>>(
-            argPack..., center_, rho0, -c, *composition_, earthRadius_);
+            argPack..., center_, rho0, -c, *composition_, planetRadius_);
       };
 
       // now unpack the additional arguments
@@ -64,7 +64,7 @@ namespace corsika {
       node->setModelProperties(std::move(model));
     } else {
       node->template setModelProperties<SlidingPlanarExponential<TMediumInterface>>(
-          center_, rho0, -c, *composition_, earthRadius_);
+          center_, rho0, -c, *composition_, planetRadius_);
     }
 
     layers_.push(std::move(node));
@@ -75,14 +75,14 @@ namespace corsika {
   inline void LayeredSphericalAtmosphereBuilder<
       TMediumInterface, TMediumModelExtra,
       TModelArgs...>::addLinearLayer(LengthType c, LengthType upperBoundary) {
-    auto const radius = earthRadius_ + upperBoundary;
+    auto const radius = planetRadius_ + upperBoundary;
     checkRadius(radius);
     previousRadius_ = radius;
 
     auto node = std::make_unique<VolumeTreeNode<TMediumInterface>>(
         std::make_unique<Sphere>(center_, radius));
 
-    units::si::GrammageType constexpr b = 1 * 1_g / (1_cm * 1_cm);
+    units::si::GrammageType constexpr b = 1_g / (1_cm * 1_cm);
     auto const rho0 = b / c;
 
     if constexpr (detail::has_extra_models<TMediumModelExtra>::value) {
@@ -94,7 +94,6 @@ namespace corsika {
 
       // now unpack the additional arguments
       auto model = std::apply(lastBound, additionalModelArgs_);
-
       node->setModelProperties(std::move(model));
     } else {
       node->template setModelProperties<HomogeneousMedium<TMediumInterface>>(
@@ -108,33 +107,33 @@ namespace corsika {
             typename... TModelArgs>
   inline void
   LayeredSphericalAtmosphereBuilder<TMediumInterface, TMediumModelExtra, TModelArgs...>::
-      addTabularLayer(GrammageType b,
-                      std::function<MassDensityType(LengthType)> const& funcRho,
-                      LengthType upperBoundary) {
+      addTabularLayer(std::function<MassDensityType(LengthType)> const& funcRho,
+                      unsigned int const nBins, LengthType const deltaHeight,
+                      LengthType const upperBoundary) {
 
-    auto const radius = earthRadius_ + upperBoundary;
+    auto const radius = planetRadius_ + upperBoundary;
     checkRadius(radius);
     previousRadius_ = radius;
 
     auto node = std::make_unique<VolumeTreeNode<TMediumInterface>>(
         std::make_unique<Sphere>(center_, radius));
-    /*
-        if constexpr (detail::has_extra_models<TMediumModelExtra>::value) {
-          // helper lambda in which the last 5 arguments to make_shared<...> are bound
-          auto lastBound = [&](auto... argPack) {
-            return std::make_shared<
-                TMediumModelExtra<SlidingPlanarTabular<TMediumInterface>>>(
-                argPack..., center_, rho0, -c, *composition_, earthRadius_);
-          };
-
-          // now unpack the additional arguments
-          auto model = std::apply(lastBound, additionalModelArgs_);
-          node->setModelProperties(std::move(model));
-        } else {
-          node->template setModelProperties<SlidingPlanarTabular<TMediumInterface>>(
-              center_, rho0, -c, *composition_, earthRadius_);
-        }
-    */
+
+    if constexpr (detail::has_extra_models<TMediumModelExtra>::value) {
+      // helper lambda in which the last 5 arguments to make_shared<...> are bound
+      auto lastBound = [&](auto... argPack) {
+        return std::make_shared<
+            TMediumModelExtra<SlidingPlanarTabular<TMediumInterface>>>(
+            argPack..., center_, funcRho, nBins, deltaHeight, *composition_,
+            planetRadius_);
+      };
+
+      // now unpack the additional arguments
+      auto model = std::apply(lastBound, additionalModelArgs_);
+      node->setModelProperties(std::move(model));
+    } else {
+      node->template setModelProperties<SlidingPlanarTabular<TMediumInterface>>(
+          center_, funcRho, nBins, deltaHeight, *composition_, planetRadius_);
+    }
     layers_.push(std::move(node));
   }
 
@@ -167,10 +166,10 @@ namespace corsika {
   template <typename TMediumInterface, template <typename> typename MExtraEnvirnoment>
   struct make_layered_spherical_atmosphere_builder {
     template <typename... TArgs>
-    static auto create(Point const& center, LengthType earthRadius, TArgs... args) {
+    static auto create(Point const& center, LengthType planetRadius, TArgs... args) {
       return LayeredSphericalAtmosphereBuilder<TMediumInterface, MExtraEnvirnoment,
                                                TArgs...>{std::forward<TArgs>(args)...,
-                                                         center, earthRadius};
+                                                         center, planetRadius};
     }
   };
 
diff --git a/corsika/detail/media/SlidingPlanarExponential.inl b/corsika/detail/media/SlidingPlanarExponential.inl
index 80fc9bf6b77624e771b4df0637903660e9a707d8..9b921872811e157c9d0d232286b3714444983727 100644
--- a/corsika/detail/media/SlidingPlanarExponential.inl
+++ b/corsika/detail/media/SlidingPlanarExponential.inl
@@ -8,50 +8,51 @@
 
 #pragma once
 
-#include <corsika/media/SlidingPlanarExponential.hpp>
-
 namespace corsika {
 
-  template <typename T>
-  inline SlidingPlanarExponential<T>::SlidingPlanarExponential(
+  template <typename TDerived>
+  inline SlidingPlanarExponential<TDerived>::SlidingPlanarExponential(
       Point const& p0, MassDensityType rho0, LengthType lambda,
       NuclearComposition const& nuclComp, LengthType referenceHeight)
-      : BaseExponential<SlidingPlanarExponential<T>>(p0, referenceHeight, rho0, lambda)
-      , nuclComp_(nuclComp)
-      , referenceHeight_(referenceHeight) {}
+      : BaseExponential<SlidingPlanarExponential<TDerived>>(p0, referenceHeight, rho0,
+                                                            lambda)
+      , nuclComp_(nuclComp) {}
 
-  template <typename T>
-  inline MassDensityType SlidingPlanarExponential<T>::getMassDensity(
+  template <typename TDerived>
+  inline MassDensityType SlidingPlanarExponential<TDerived>::getMassDensity(
       Point const& point) const {
     auto const heightFull =
-        (point - BaseExponential<SlidingPlanarExponential<T>>::getAnchorPoint())
+        (point - BaseExponential<SlidingPlanarExponential<TDerived>>::getAnchorPoint())
             .getNorm();
-    return BaseExponential<SlidingPlanarExponential<T>>::getMassDensity(heightFull);
+    return BaseExponential<SlidingPlanarExponential<TDerived>>::getMassDensity(
+        heightFull);
   }
 
-  template <typename T>
-  inline NuclearComposition const& SlidingPlanarExponential<T>::getNuclearComposition()
-      const {
+  template <typename TDerived>
+  inline NuclearComposition const&
+  SlidingPlanarExponential<TDerived>::getNuclearComposition() const {
     return nuclComp_;
   }
 
-  template <typename T>
-  inline GrammageType SlidingPlanarExponential<T>::getIntegratedGrammage(
+  template <typename TDerived>
+  inline GrammageType SlidingPlanarExponential<TDerived>::getIntegratedGrammage(
       BaseTrajectory const& traj) const {
-    auto const axis = (traj.getPosition(0) -
-                       BaseExponential<SlidingPlanarExponential<T>>::getAnchorPoint())
-                          .normalized();
-    return BaseExponential<SlidingPlanarExponential<T>>::getIntegratedGrammage(traj,
-                                                                               axis);
+    auto const axis =
+        (traj.getPosition(0) -
+         BaseExponential<SlidingPlanarExponential<TDerived>>::getAnchorPoint())
+            .normalized();
+    return BaseExponential<SlidingPlanarExponential<TDerived>>::getIntegratedGrammage(
+        traj, axis);
   }
 
-  template <typename T>
-  inline LengthType SlidingPlanarExponential<T>::getArclengthFromGrammage(
+  template <typename TDerived>
+  inline LengthType SlidingPlanarExponential<TDerived>::getArclengthFromGrammage(
       BaseTrajectory const& traj, GrammageType const grammage) const {
-    auto const axis = (traj.getPosition(0) -
-                       BaseExponential<SlidingPlanarExponential<T>>::getAnchorPoint())
-                          .normalized();
-    return BaseExponential<SlidingPlanarExponential<T>>::getArclengthFromGrammage(
+    auto const axis =
+        (traj.getPosition(0) -
+         BaseExponential<SlidingPlanarExponential<TDerived>>::getAnchorPoint())
+            .normalized();
+    return BaseExponential<SlidingPlanarExponential<TDerived>>::getArclengthFromGrammage(
         traj, grammage, axis);
   }
 
diff --git a/corsika/media/BaseExponential.hpp b/corsika/media/BaseExponential.hpp
index 27a267df6061947843d55bac73362adea9ca0f6f..507ed640fa130eda007a5e71a3dec3437a530d9b 100644
--- a/corsika/media/BaseExponential.hpp
+++ b/corsika/media/BaseExponential.hpp
@@ -29,8 +29,6 @@ namespace corsika {
                     MassDensityType rho0, LengthType lambda);
 
     Point const& getAnchorPoint() const { return point_; }
-    MassDensityType getRho0() const { return rho0_; }
-    InverseLengthType getInvLambda() const { return invLambda_; }
 
   protected:
     auto const& getImplementation() const;
diff --git a/corsika/media/LayeredSphericalAtmosphereBuilder.hpp b/corsika/media/LayeredSphericalAtmosphereBuilder.hpp
index 41ad158011dfb7215abca026a84b082736ccc0e0..6c178f4589537fff2a79c7e166662791c30a1306 100644
--- a/corsika/media/LayeredSphericalAtmosphereBuilder.hpp
+++ b/corsika/media/LayeredSphericalAtmosphereBuilder.hpp
@@ -22,6 +22,7 @@
 #include <stack>
 #include <tuple>
 #include <type_traits>
+#include <functional>
 
 namespace corsika {
 
@@ -68,9 +69,9 @@ namespace corsika {
 
   protected:
     LayeredSphericalAtmosphereBuilder(TModelArgs... args, Point const& center,
-                                      LengthType earthRadius)
+                                      LengthType planetRadius)
         : center_(center)
-        , earthRadius_(earthRadius)
+        , planetRadius_(planetRadius)
         , additionalModelArgs_{args...} {}
 
   public:
@@ -78,9 +79,9 @@ namespace corsika {
     void addExponentialLayer(GrammageType b, LengthType c, LengthType upperBoundary);
     void addLinearLayer(LengthType c, LengthType upperBoundary);
 
-    void addTabularLayer(GrammageType b,
-                         std::function<MassDensityType(LengthType)> const& funcRho,
-                         LengthType upperBoundary);
+    void addTabularLayer(std::function<MassDensityType(LengthType)> const& funcRho,
+                         unsigned int const nBins, LengthType const deltaHeight,
+                         LengthType const upperBoundary);
 
     int getSize() const { return layers_.size(); }
 
@@ -88,9 +89,9 @@ namespace corsika {
     Environment<TMediumInterface> assemble();
 
     /**
-     * Get the current Earth radius.
+     * Get the current planet radius.
      */
-    LengthType getEarthRadius() const { return earthRadius_; }
+    LengthType getPlanetRadius() const { return planetRadius_; }
 
   private:
     void checkRadius(LengthType r) const;
@@ -98,7 +99,7 @@ namespace corsika {
     std::unique_ptr<NuclearComposition> composition_;
     Point center_;
     LengthType previousRadius_{LengthType::zero()};
-    LengthType earthRadius_;
+    LengthType planetRadius_;
     std::tuple<TModelArgs...> const additionalModelArgs_;
 
     std::stack<typename VolumeTreeNode<TMediumInterface>::VTNUPtr>
diff --git a/corsika/media/SlidingPlanarExponential.hpp b/corsika/media/SlidingPlanarExponential.hpp
index 63f8238ba2139899957fb4ff12d6e1e1b6b0f7e0..4ef91bb4e1ca338241c61d805f1fa3dd68c47ba4 100644
--- a/corsika/media/SlidingPlanarExponential.hpp
+++ b/corsika/media/SlidingPlanarExponential.hpp
@@ -13,7 +13,6 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
-#include <corsika/media/FlatExponential.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 #include <corsika/framework/geometry/BaseTrajectory.hpp>
 
@@ -53,7 +52,6 @@ namespace corsika {
 
   private:
     NuclearComposition const nuclComp_;
-    LengthType const referenceHeight_;
   };
 
 } // namespace corsika
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index e3d056e22202e7af786c3f1eec00fcc36e077a61..70816c0d30968b67caa65f98b204c4ab0b09c338 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -60,4 +60,6 @@ CORSIKA_REGISTER_EXAMPLE (hybrid_MC RUN_OPTIONS 4 2 10000.)
 add_executable (corsika corsika.cpp)
 target_link_libraries (corsika CORSIKA8::CORSIKA8)
 
+add_executable (mars mars.cpp)
+target_link_libraries (mars CORSIKA8::CORSIKA8)
 
diff --git a/examples/corsika.cpp b/examples/corsika.cpp
index fd048470df3c3d4c598ab35c0be67b3db0ebf0e1..ee424c1de386a288b934c1f6990e1cb44ff1e214 100644
--- a/examples/corsika.cpp
+++ b/examples/corsika.cpp
@@ -103,8 +103,6 @@ using MyExtraEnv = MediumPropertyModel<UniformMagneticField<T>>;
 
 int main(int argc, char** argv) {
 
-  logging::set_level(logging::level::info);
-
   // the main command line description
   CLI::App app{"Simulate standard (downgoing) showers with CORSIKA 8."};
 
@@ -154,10 +152,31 @@ int main(int argc, char** argv) {
       ->group("Misc.");
   app.add_flag("--force-interaction", "Force the location of the first interaction.")
       ->group("Misc.");
+  app.add_option("-v,--verbosity", "Verbosity level: warn, info, debug, trace.")
+      ->default_val("info")
+      ->check(CLI::IsMember({"warn", "info", "debug", "trace"}))
+      ->group("Misc.");
 
   // parse the command line options into the variables
   CLI11_PARSE(app, argc, argv);
 
+  if (app.count("--verbosity")) {
+    string const loglevel = app["verbosity"]->as<string>();
+    if (loglevel == "warn") {
+      logging::set_level(logging::level::warn);
+    } else if (loglevel == "info") {
+      logging::set_level(logging::level::info);
+    } else if (loglevel == "debug") {
+      logging::set_level(logging::level::debug);
+    } else if (loglevel == "trace") {
+#ifndef DEBUG
+      CORSIKA_LOG_ERROR("trace log level requires a Debug build.");
+      return 1;
+#endif
+      logging::set_level(logging::level::trace);
+    }
+  }
+
   // check that we got either PDG or A/Z
   // this can be done with option_groups but the ordering
   // gets all messed up
@@ -185,17 +204,29 @@ int main(int argc, char** argv) {
                                                                            50_uT, 0_T});
   builder.setNuclearComposition(
       {{Code::Nitrogen, Code::Oxygen},
-       {0.7847f, 1.f - 0.7847f}}); // values taken from AIRES manual, Ar removed for now
+       {0.7847, 1. - 0.7847}}); // values taken from AIRES manual, Ar removed for now
 
   builder.addExponentialLayer(1222.6562_g / (1_cm * 1_cm), 994186.38_cm, 2_km);
   builder.addExponentialLayer(1222.6562_g / (1_cm * 1_cm), 994186.38_cm, 4_km);
   builder.addExponentialLayer(1144.9069_g / (1_cm * 1_cm), 878153.55_cm, 10_km);
   builder.addExponentialLayer(1305.5948_g / (1_cm * 1_cm), 636143.04_cm, 40_km);
   builder.addExponentialLayer(540.1778_g / (1_cm * 1_cm), 772170.16_cm, 100_km);
-  builder.addLinearLayer(1e9_cm, 112.8_km + constants::EarthRadius::Mean);
+  builder.addLinearLayer(1e9_cm, 112.8_km);
   builder.assemble(env);
   /* === END: SETUP ENVIRONMENT AND ROOT COORDINATE SYSTEM === */
 
+  ofstream atmout("earth.dat");
+  for (LengthType h = 0_m; h < 110_km; h += 100_m) {
+    Point const ptest{rootCS, 0_m, 0_m, builder.getPlanetRadius() + h};
+    auto rho =
+        env.getUniverse()->getContainingNode(ptest)->getModelProperties().getMassDensity(
+            ptest);
+    atmout << h / 1_m << " " << rho / 1_kg * cube(1_m) << "\n";
+  }
+  atmout.close();
+
+  /* === START: CONSTRUCT PRIMARY PARTICLE === */
+
   /* === START: CONSTRUCT PRIMARY PARTICLE === */
 
   // parse the primary ID as a PDG or A/Z code
@@ -231,8 +262,8 @@ int main(int argc, char** argv) {
   /* === END: CONSTRUCT PRIMARY PARTICLE === */
 
   /* === START: CONSTRUCT GEOMETRY === */
-  auto const observationHeight = 0_km + builder.getEarthRadius();
-  auto const injectionHeight = 111.75_km + builder.getEarthRadius();
+  auto const observationHeight = 0_km + builder.getPlanetRadius();
+  auto const injectionHeight = 111.75_km + builder.getPlanetRadius();
   auto const t = -observationHeight * cos(thetaRad) +
                  sqrt(-static_pow<2>(sin(thetaRad) * observationHeight) +
                       static_pow<2>(injectionHeight));
@@ -281,9 +312,9 @@ int main(int argc, char** argv) {
       Code::KStar0_1430_MinusBar,
   }};
 
-  decaySibyll.printDecayConfig();
+  // decaySibyll.printDecayConfig();
 
-  ParticleCut cut{50_GeV, 50_GeV, 50_GeV, 50_GeV, false};
+  ParticleCut cut{1_GeV, 1_GeV, 1_GeV, 1_GeV, false};
   corsika::proposal::Interaction emCascade(env);
   corsika::proposal::ContinuousProcess emContinuous(env);
   InteractionCounter emCascadeCounted(emCascade);
diff --git a/examples/em_shower.cpp b/examples/em_shower.cpp
index 0ff4d28b7425d1fd988af0718ccecbe84b4b8ba1..d8b8385f3696ff1fee4f48518253bd24e97fbdb0 100644
--- a/examples/em_shower.cpp
+++ b/examples/em_shower.cpp
@@ -128,8 +128,8 @@ int main(int argc, char** argv) {
   cout << "input momentum: " << plab.getComponents() / 1_GeV
        << ", norm = " << plab.getNorm() << endl;
 
-  auto const observationHeight = 1.4_km + builder.getEarthRadius();
-  auto const injectionHeight = 112.75_km + builder.getEarthRadius();
+  auto const observationHeight = 1.4_km + builder.getPlanetRadius();
+  auto const injectionHeight = 112.75_km + builder.getPlanetRadius();
   auto const t = -observationHeight * cos(thetaRad) +
                  sqrt(-static_pow<2>(sin(thetaRad) * observationHeight) +
                       static_pow<2>(injectionHeight));
diff --git a/examples/hybrid_MC.cpp b/examples/hybrid_MC.cpp
index e065c4ee8ddc787e2bd56d2f8502f37cfb7d0bd1..4674512cc4d0f204538999c6bf6a3eb384704ea2 100644
--- a/examples/hybrid_MC.cpp
+++ b/examples/hybrid_MC.cpp
@@ -178,8 +178,8 @@ int main(int argc, char** argv) {
   cout << "input momentum: " << plab.getComponents() / 1_GeV
        << ", norm = " << plab.getNorm() << endl;
 
-  auto const observationHeight = 0_km + builder.getEarthRadius();
-  auto const injectionHeight = 112.75_km + builder.getEarthRadius();
+  auto const observationHeight = 0_km + builder.getPlanetRadius();
+  auto const injectionHeight = 112.75_km + builder.getPlanetRadius();
   auto const t = -observationHeight * cos(thetaRad) +
                  sqrt(-static_pow<2>(sin(thetaRad) * observationHeight) +
                       static_pow<2>(injectionHeight));
diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp
index 6ae766bb197e2e6610aa503442cea05704652b3b..6800a84be8858d31ed01d36d213ef56754a265b1 100644
--- a/examples/vertical_EAS.cpp
+++ b/examples/vertical_EAS.cpp
@@ -177,8 +177,8 @@ int main(int argc, char** argv) {
   cout << "input momentum: " << plab.getComponents() / 1_GeV
        << ", norm = " << plab.getNorm() << endl;
 
-  auto const observationHeight = 0_km + builder.getEarthRadius();
-  auto const injectionHeight = 111.75_km + builder.getEarthRadius();
+  auto const observationHeight = 0_km + builder.getPlanetRadius();
+  auto const injectionHeight = 111.75_km + builder.getPlanetRadius();
   auto const t = -observationHeight * cos(thetaRad) +
                  sqrt(-static_pow<2>(sin(thetaRad) * observationHeight) +
                       static_pow<2>(injectionHeight));
diff --git a/tests/media/testEnvironment.cpp b/tests/media/testEnvironment.cpp
index 88e143b7be1d66e0e349284882cedb6002636fff..fffdff7fe04389a0feb7d9471a83ed3b7740d145 100644
--- a/tests/media/testEnvironment.cpp
+++ b/tests/media/testEnvironment.cpp
@@ -11,6 +11,7 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/RootCoordinateSystem.hpp>
 #include <corsika/framework/geometry/Vector.hpp>
+
 #include <corsika/media/DensityFunction.hpp>
 #include <corsika/media/FlatExponential.hpp>
 #include <corsika/media/HomogeneousMedium.hpp>
@@ -26,6 +27,7 @@
 #include <corsika/media/LinearApproximationIntegrator.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 #include <corsika/media/SlidingPlanarExponential.hpp>
+#include <corsika/media/SlidingPlanarTabular.hpp>
 #include <corsika/media/VolumeTreeNode.hpp>
 
 #include <SetupTestTrajectory.hpp>
@@ -94,7 +96,7 @@ TEST_CASE("FlatExponential") {
 
   Vector const axis(gCS, QuantityVector<dimensionless_d>(0, 0, 1));
   LengthType const lambda = 3_m;
-  auto const rho0 = 1_g / static_pow<3>(1_cm);
+  auto const rho0 = 1_g / cube(1_cm);
   FlatExponential<IMediumModel> const medium(gOrigin, axis, rho0, lambda,
                                              protonComposition);
   SpeedType const speed = 20_m / second;
@@ -188,6 +190,256 @@ TEST_CASE("SlidingPlanarExponential") {
   }
 }
 
+struct RhoFuncConst {
+  MassDensityType operator()(LengthType) const { return 1_g / cube(1_cm); }
+  static GrammageType integrate(LengthType dL) { return dL * 1_g / cube(1_cm); }
+};
+
+struct RhoFuncExp {
+  MassDensityType operator()(LengthType height) const {
+    return 1_g / cube(1_cm) * exp(-height / 1000_m);
+  }
+  static GrammageType integrate(BaseTrajectory const& traj, Point const& origin,
+                                LengthType const& refH) {
+    LengthType height1 = (traj.getPosition(0) - origin).getNorm() - refH;
+    LengthType height2 = (traj.getPosition(1) - origin).getNorm() - refH;
+    if (height1 > height2) { std::swap(height1, height2); }
+
+    DirectionVector const axis(
+        (traj.getPosition(0) - origin).normalized()); // to gravity center
+    double const cosTheta = axis.dot(traj.getDirection(0));
+
+    CORSIKA_LOG_INFO("h1={} h2={} cT={} rho1={}, rho2={}", height1, height2, cosTheta,
+                     1_g / cube(1_cm) * exp(-height1 / 1000_m),
+                     1_g / cube(1_cm) * exp(-height2 / 1000_m));
+    return (1_km * 1_g / cube(1_cm) * exp(-height1 / 1000_m) -
+            1_km * 1_g / cube(1_cm) * exp(-height2 / 1000_m)) /
+           cosTheta;
+  }
+  static GrammageType integrate(BaseTrajectory const& traj, LengthType const& length,
+                                Point const& origin, LengthType const& refH) {
+    LengthType height1 = (traj.getPosition(0) - origin).getNorm() - refH;
+    LengthType height2 =
+        (traj.getPosition(0) + traj.getDirection(0) * length - origin).getNorm() - refH;
+    if (height1 > height2) { std::swap(height1, height2); }
+
+    DirectionVector const axis(
+        (traj.getPosition(0) - origin).normalized()); // to gravity center
+    double const cosTheta = axis.dot(traj.getDirection(0));
+
+    CORSIKA_LOG_INFO("h1={} h2={} cT={}", height1, height2, cosTheta);
+    return (1_km * 1_g / cube(1_cm) * exp(-height1 / 1000_m) -
+            1_km * 1_g / cube(1_cm) * exp(-height2 / 1000_m)) /
+           cosTheta;
+  }
+};
+
+TEST_CASE("SlidingPlanarTabular") {
+
+  logging::set_level(logging::level::info);
+
+  NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
+                                             std::vector<float>{1.f});
+
+  RhoFuncConst rhoFunc;
+  SlidingPlanarTabular<IMediumModel> const medium(gOrigin, rhoFunc, 1000, 10_m,
+                                                  protonComposition);
+
+  SECTION("density") {
+    CHECK(medium.getMassDensity({gCS, {0_m, 0_m, 3_m}}) /
+              medium.getMassDensity({gCS, {0_m, 3_m, 0_m}}) ==
+          Approx(1));
+    CHECK(medium.getMassDensity({gCS, {0_mm, 0_m, 3_m}}) == 1_g / cube(1_cm));
+    CHECK(medium.getMassDensity({gCS, {0_mm, 0_m, 300_m}}) == 1_g / cube(1_cm));
+  }
+
+  SECTION("vertical") {
+    SpeedType const speed = 5_m / second;
+    TimeType const tEnd1 = 1_s;
+    LengthType const length1 = speed * tEnd1;
+    TimeType const tEnd2 = 300_s;
+    LengthType const length2 = speed * tEnd2;
+    Line const line(
+        {gCS, {0_m, 0_m, 1_m}},
+        Vector<SpeedType::dimension_type>(gCS, {0_m / second, 0_m / second, speed}));
+    setup::Trajectory const trajectory1 =
+        setup::testing::make_track<setup::Trajectory>(line, tEnd1);
+    Line const line1Reverse(
+        trajectory1.getPosition(1),
+        Vector<SpeedType::dimension_type>(gCS, {0_m / second, 0_m / second, -speed}));
+    setup::Trajectory const trajectory1Reverse =
+        setup::testing::make_track<setup::Trajectory>(line1Reverse, tEnd1);
+
+    setup::Trajectory const trajectory2 =
+        setup::testing::make_track<setup::Trajectory>(line, tEnd2);
+    Line const line2Reverse(
+        trajectory2.getPosition(0),
+        Vector<SpeedType::dimension_type>(gCS, {0_m / second, 0_m / second, -speed}));
+    setup::Trajectory const trajectory2Reverse =
+        setup::testing::make_track<setup::Trajectory>(line2Reverse, tEnd2);
+
+    // failures
+    CHECK_THROWS(medium.getArclengthFromGrammage(trajectory1, -1_kg / square(1_cm)));
+
+    MassDensityType const rho0 = 1_g / cube(1_cm);
+
+    // short track
+    CHECK(medium.getIntegratedGrammage(trajectory1) == length1 * rho0);
+    LengthType const testD1 = length1 / 200; // within bin
+    CHECK(medium.getArclengthFromGrammage(trajectory1, rho0 * testD1) / testD1 ==
+          Approx(1));
+    // short track, reverse
+    CHECK(medium.getIntegratedGrammage(trajectory1Reverse) == length1 * rho0);
+    CHECK(medium.getArclengthFromGrammage(trajectory1Reverse, rho0 * testD1) / testD1 ==
+          Approx(1));
+
+    // long track
+    CHECK(medium.getIntegratedGrammage(trajectory2) == length2 * 1_g / cube(1_cm));
+    LengthType const testD2 = length2 / 25; // multi bin
+    CHECK(medium.getArclengthFromGrammage(trajectory2, rho0 * testD2) == testD2);
+  }
+
+  SECTION("inclined") {
+    SpeedType const speed = 5_m / second;
+    TimeType const tEnd1 = 1_s;
+    LengthType const length1 = speed * tEnd1;
+    TimeType const tEnd2 = 300_s;
+    LengthType const length2 = speed * tEnd2;
+    Line const line({gCS, {0_m, 0_m, 1_m}},
+                    Vector<SpeedType::dimension_type>(
+                        gCS, {speed / sqrt(2.), 0_m / second, speed / sqrt(2.)}));
+    setup::Trajectory const trajectory1 =
+        setup::testing::make_track<setup::Trajectory>(line, tEnd1);
+    Line const line1Reverse(
+        trajectory1.getPosition(1),
+        Vector<SpeedType::dimension_type>(
+            gCS, {-speed / sqrt(2.), 0_m / second, -speed / sqrt(2.)}));
+    setup::Trajectory const trajectory1Reverse =
+        setup::testing::make_track<setup::Trajectory>(line1Reverse, tEnd1);
+
+    setup::Trajectory const trajectory2 =
+        setup::testing::make_track<setup::Trajectory>(line, tEnd2);
+    Line const line2Reverse(
+        trajectory2.getPosition(1),
+        Vector<SpeedType::dimension_type>(
+            gCS, {-speed / sqrt(2.), 0_m / second, -speed / sqrt(2.)}));
+    setup::Trajectory const trajectory2Reverse =
+        setup::testing::make_track<setup::Trajectory>(line2Reverse, tEnd2);
+
+    MassDensityType const rho0 = 1_g / cube(1_cm);
+
+    // short track
+    CHECK(medium.getIntegratedGrammage(trajectory1) / (length1 * rho0) == Approx(1));
+    LengthType const testD1 = length1 / 200; // within bin
+    CHECK(medium.getArclengthFromGrammage(trajectory1, RhoFuncConst::integrate(testD1)) /
+              testD1 ==
+          Approx(1));
+    // short track, reverse
+    CHECK(medium.getIntegratedGrammage(trajectory1Reverse) / (length1 * rho0) ==
+          Approx(1));
+    CHECK(medium.getArclengthFromGrammage(trajectory1Reverse, rho0 * testD1) / testD1 ==
+          Approx(1));
+
+    // long track
+    CHECK(medium.getIntegratedGrammage(trajectory2) / (length2 * rho0) ==
+          Approx(1).epsilon(0.01));
+    LengthType const testD2 = length2 / 25; // multi bin
+    CHECK(medium.getArclengthFromGrammage(trajectory2, rho0 * testD2) / testD2 ==
+          Approx(1).epsilon(0.01));
+    // long track reverse
+    CORSIKA_LOG_INFO("length2={}", length2);
+    CHECK(medium.getIntegratedGrammage(trajectory2Reverse) / (length2 * rho0) ==
+          Approx(1).epsilon(0.01));
+    CHECK(medium.getArclengthFromGrammage(trajectory2Reverse, rho0 * testD2) / testD2 ==
+          Approx(1).epsilon(0.01));
+  }
+
+  /*The exponential test is taken over phase-space where the exponential is not so steep
+   * and is samples in sufficient substeps. An reference-height offset of 1000_km is used.
+   * Thus, density is given from 1000 to 1010 km. And curvature effects are small.
+   */
+
+  RhoFuncExp rhoFuncExp;
+  SlidingPlanarTabular<IMediumModel> const mediumExp(gOrigin, rhoFuncExp, 1000, 10_m,
+                                                     protonComposition, 1000_km);
+
+  SECTION("exponential") {
+
+    SpeedType const speed = 5_m / second;
+    TimeType const tEnd1 = 1_s;
+    LengthType const length1 = speed * tEnd1;
+    TimeType const tEnd2 = 300_s;
+    LengthType const length2 = speed * tEnd2;
+    Line const line({gCS, {0_m, 0_m, 1000.005_km}},
+                    Vector<SpeedType::dimension_type>(
+                        gCS, {speed / sqrt(2.), 0_m / second, speed / sqrt(2.)}));
+    setup::Trajectory const trajectory1 =
+        setup::testing::make_track<setup::Trajectory>(line, tEnd1);
+    Line const line1Reverse(
+        trajectory1.getPosition(1),
+        Vector<SpeedType::dimension_type>(
+            gCS, {-speed / sqrt(2.), 0_m / second, -speed / sqrt(2.)}));
+    setup::Trajectory const trajectory1Reverse =
+        setup::testing::make_track<setup::Trajectory>(line1Reverse, tEnd1);
+
+    setup::Trajectory const trajectory2 =
+        setup::testing::make_track<setup::Trajectory>(line, tEnd2);
+
+    CORSIKA_LOG_INFO("{} {}", RhoFuncExp::integrate(trajectory1, gOrigin, 1000_km),
+                     length1);
+
+    // short track
+    GrammageType const testShortX = RhoFuncExp::integrate(trajectory1, gOrigin, 1000_km);
+    CHECK(mediumExp.getIntegratedGrammage(trajectory1) / testShortX ==
+          Approx(1).epsilon(0.01));
+    LengthType const testD1 = length1 / 200; // within bin
+    GrammageType const testD1X =
+        RhoFuncExp::integrate(trajectory1, testD1, gOrigin, 1000_km);
+    CHECK(mediumExp.getArclengthFromGrammage(trajectory1, testD1X) / testD1 ==
+          Approx(1).epsilon(0.01));
+    // short track, reverse
+    CHECK(mediumExp.getIntegratedGrammage(trajectory1Reverse) / testShortX ==
+          Approx(1).epsilon(0.01));
+    CHECK(mediumExp.getArclengthFromGrammage(trajectory1Reverse, testD1X) / testD1 ==
+          Approx(1).epsilon(0.01));
+
+    // long track
+    GrammageType const testLongX = RhoFuncExp::integrate(trajectory2, gOrigin, 1000_km);
+    CORSIKA_LOG_INFO("testLongX={}", testLongX);
+    CHECK(mediumExp.getIntegratedGrammage(trajectory2) / testLongX ==
+          Approx(1).epsilon(0.01));
+    LengthType const testD2 = length2 / 25; // multi bin
+    GrammageType const testD2X =
+        RhoFuncExp::integrate(trajectory2, testD2, gOrigin, 1000_km);
+    CHECK(mediumExp.getArclengthFromGrammage(trajectory2, testD2X) / testD2 ==
+          Approx(1).epsilon(0.01));
+    // long track, reverse
+
+    // first full trajectory2 reverse
+    Line line2Reverse(trajectory2.getPosition(1),
+                      Vector<SpeedType::dimension_type>(
+                          gCS, {-speed / sqrt(2.), 0_m / second, -speed / sqrt(2.)}));
+    setup::Trajectory trajectory2Reverse =
+        setup::testing::make_track<setup::Trajectory>(line2Reverse, tEnd2);
+
+    CHECK(mediumExp.getIntegratedGrammage(trajectory2Reverse) / testLongX ==
+          Approx(1).epsilon(0.01));
+
+    // but now shorter trajectory2 reversed to correspond 100% to testD2
+
+    line2Reverse = Line(trajectory2.getPosition(0) + trajectory2.getDirection(0) * testD2,
+                        Vector<SpeedType::dimension_type>(
+                            gCS, {-speed / sqrt(2.), 0_m / second, -speed / sqrt(2.)}));
+    auto const trajectory2ReverseShort =
+        setup::testing::make_track<setup::Trajectory>(line2Reverse, testD2 / speed);
+
+    CORSIKA_LOG_INFO("here {} {} {}", trajectory2ReverseShort.getLength(), testD2,
+                     testD2X / 1_g * square(1_cm));
+    CHECK(mediumExp.getArclengthFromGrammage(trajectory2ReverseShort, testD2X) / testD2 ==
+          Approx(1).epsilon(0.01));
+  }
+}
+
 MassDensityType constexpr rho0 = 1_kg / 1_m / 1_m / 1_m;
 
 struct ExponentialTest {
@@ -289,7 +541,7 @@ TEST_CASE("LayeredSphericalAtmosphereBuilder") {
 
   CHECK(builder.getSize() == 0);
 
-  auto const R = builder.getEarthRadius();
+  auto const R = builder.getPlanetRadius();
 
   CHECK(univ->getChildNodes().size() == 1);
 
@@ -334,7 +586,7 @@ TEST_CASE("LayeredSphericalAtmosphereBuilder w/ magnetic field") {
 
   CHECK(builder.getSize() == 0);
   CHECK(univ->getChildNodes().size() == 1);
-  auto const R = builder.getEarthRadius();
+  auto const R = builder.getPlanetRadius();
 
   // check magnetic field at several locations
   const Point pTest(gCS, -10_m, 4_m, R + 35_m);