diff --git a/corsika/detail/framework/geometry/Cubic.inl b/corsika/detail/framework/geometry/Box.inl
similarity index 63%
rename from corsika/detail/framework/geometry/Cubic.inl
rename to corsika/detail/framework/geometry/Box.inl
index 5e64f3abaf8fdbf8cc88ad5b9bfc75529312b019..d5b942ec64ecd22021dedc53ddcf4ee1870a8f5f 100644
--- a/corsika/detail/framework/geometry/Cubic.inl
+++ b/corsika/detail/framework/geometry/Box.inl
@@ -1,14 +1,12 @@
-
-
 namespace corsika {
-  inline bool Cubic::contains(Point const& p) const {
+  inline bool Box::contains(Point const& p) const {
     if ((abs(p.getX(cs_)) < x_) && (abs(p.getY(cs_)) < y_) && (abs(p.getZ(cs_)) < z_))
       return true;
     else
       return false;
   }
 
-  inline std::string Cubic::asString() const {
+  inline std::string Box::asString() const {
     std::ostringstream txt;
     txt << "center=" << center_ << ", x-axis=" << DirectionVector{cs_, {1, 0, 0}}
         << ", y-axis: " << DirectionVector{cs_, {0, 1, 0}}
@@ -16,4 +14,9 @@ namespace corsika {
     return txt.str();
   }
 
+  template <typename TDim>
+  inline void Box::rotate(QuantityVector<TDim> const& axis, double const angle) {
+    cs_ = make_rotation(cs_, axis, angle);
+  }
+
 } // namespace corsika
\ No newline at end of file
diff --git a/corsika/detail/modules/ObservationCubic.inl b/corsika/detail/modules/ObservationCubic.inl
deleted file mode 100644
index 1504c67bc454336bd8af9bb55d09ce4e784e0d16..0000000000000000000000000000000000000000
--- a/corsika/detail/modules/ObservationCubic.inl
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * (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.
- */
-
-namespace corsika {
-
-  template <typename TTracking, typename TOutput>
-  ObservationCubic<TTracking, TOutput>::ObservationCubic(
-      Point const& center, CoordinateSystemPtr cs, LengthType const x, LengthType const y,
-      LengthType const z, bool deleteOnHit)
-      : Cubic(center, cs, x, y, z)
-      , deleteOnHit_(deleteOnHit)
-      , energy_(0_GeV)
-      , count_(0) {}
-
-  template <typename TTracking, typename TOutput>
-  template <typename TParticle, typename TTrajectory>
-  inline ProcessReturn ObservationCubic<TTracking, TOutput>::doContinuous(
-      TParticle& particle, TTrajectory& step, bool const stepLimit) {
-    /*
-       The current step did not yet reach the ObservationCubic, do nothing now and
-       wait:
-     */
-    if (!stepLimit) {
-      // @todo this is actually needed to fix small instabilities of the leap-frog
-      // tracking: Note, this is NOT a general solution and should be clearly
-      // revised with a more robust tracking. #ifdef DEBUG
-      if (deleteOnHit_) {
-        // since this is basically a bug, it cannot be tested LCOV_EXCL_START
-        LengthType const check = 1_m; // TODO, do I need to check?
-        if (check < 0_m) {
-          CORSIKA_LOG_WARN("PARTICLE AVOIDED ObservationCubic {}", check);
-          CORSIKA_LOG_WARN("Temporary fix: write and remove particle.");
-        } else
-          return ProcessReturn::Ok;
-        // LCOV_EXCL_STOP
-      } else
-        // #endif
-        return ProcessReturn::Ok;
-    }
-
-    HEPEnergyType const energy = particle.getEnergy();
-    Point const pointOfIntersection = step.getPosition(1);
-    DirectionVector const dirction = particle.getDirection();
-
-    // add our particles to the output file stream
-    this->write(particle.getPID(), energy, pointOfIntersection.getX(cs_),
-                pointOfIntersection.getY(cs_), pointOfIntersection.getZ(cs_),
-                dirction.getX(cs_), dirction.getY(cs_), dirction.getZ(cs_),
-                particle.getTime());
-
-    CORSIKA_LOG_TRACE("Particle detected absorbed={}", deleteOnHit_);
-
-    if (deleteOnHit_) {
-      count_++;
-      energy_ += energy;
-      return ProcessReturn::ParticleAbsorbed;
-    } else {
-      return ProcessReturn::Ok;
-    }
-  }
-
-  template <typename TTracking, typename TOutput>
-  template <typename TParticle, typename TTrajectory>
-  inline LengthType ObservationCubic<TTracking, TOutput>::getMaxStepLength(
-      TParticle const& particle, TTrajectory const& trajectory) {
-
-    CORSIKA_LOG_TRACE("getMaxStepLength, particle={}, pos={}, dir={}, cubic={}",
-                      particle.asString(), particle.getPosition(),
-                      particle.getDirection(), asString());
-
-    auto const intersection =
-        TTracking::intersect(particle, static_cast<Cubic const>(*this));
-
-    TimeType const timeOfIntersection = intersection.getEntry();
-    CORSIKA_LOG_TRACE("timeOfIntersection={}", timeOfIntersection);
-    if (timeOfIntersection < TimeType::zero()) {
-      return std::numeric_limits<double>::infinity() * 1_m;
-    }
-    if (timeOfIntersection > trajectory.getDuration()) {
-      return std::numeric_limits<double>::infinity() * 1_m;
-    }
-    double const fractionOfIntersection = timeOfIntersection / trajectory.getDuration();
-    CORSIKA_LOG_TRACE("ObservationCubic: getMaxStepLength dist={} m, pos={}",
-                      trajectory.getLength(fractionOfIntersection) / 1_m,
-                      trajectory.getPosition(fractionOfIntersection));
-    return trajectory.getLength(fractionOfIntersection);
-  }
-
-  template <typename TTracking, typename TOutput>
-  inline void ObservationCubic<TTracking, TOutput>::showResults() const {
-    CORSIKA_LOG_INFO(
-        " ******************************\n"
-        " ObservationCubic: \n"
-        " energy at cubic (GeV)     :  {}\n"
-        " no. of particles at cubic :  {}\n"
-        " ******************************",
-        energy_ / 1_GeV, count_);
-  }
-
-  template <typename TTracking, typename TOutput>
-  inline YAML::Node ObservationCubic<TTracking, TOutput>::getConfig() const {
-    using namespace units::si;
-
-    // construct the top-level node
-    YAML::Node node;
-
-    // basic info
-    node["type"] = "ObservationCubic";
-    node["units"] = "m"; // add default units for values
-
-    // save each component in its native coordinate system
-    auto const root_cs = get_root_CoordinateSystem();
-    node["center"].push_back(center_.getX(root_cs) / 1_m);
-    node["center"].push_back(center_.getY(root_cs) / 1_m);
-    node["center"].push_back(center_.getZ(root_cs) / 1_m);
-
-    // the x-axis vector
-    DirectionVector const x_axis = DirectionVector{cs_, {1, 0, 0}};
-    node["x-axis"].push_back(x_axis.getX(root_cs).magnitude());
-    node["x-axis"].push_back(x_axis.getY(root_cs).magnitude());
-    node["x-axis"].push_back(x_axis.getZ(root_cs).magnitude());
-
-    // the y-axis vector
-    DirectionVector const y_axis = DirectionVector{cs_, {0, 1, 0}};
-    node["y-axis"].push_back(y_axis.getX(root_cs).magnitude());
-    node["y-axis"].push_back(y_axis.getY(root_cs).magnitude());
-    node["y-axis"].push_back(y_axis.getZ(root_cs).magnitude());
-
-    // the x-axis vector
-    DirectionVector const z_axis = DirectionVector{cs_, {0, 0, 1}};
-    node["z-axis"].push_back(z_axis.getX(root_cs).magnitude());
-    node["z-axis"].push_back(z_axis.getY(root_cs).magnitude());
-    node["z-axis"].push_back(z_axis.getZ(root_cs).magnitude());
-
-    node["delete_on_hit"] = deleteOnHit_;
-
-    return node;
-  }
-
-  template <typename TTracking, typename TOutput>
-  inline void ObservationCubic<TTracking, TOutput>::reset() {
-    energy_ = 0_GeV;
-    count_ = 0;
-  }
-
-} // namespace corsika
diff --git a/corsika/detail/modules/tracking/TrackingStraight.inl b/corsika/detail/modules/tracking/TrackingStraight.inl
index a54155f6f3d42c6f78471180f90b07c59ad943ca..f596cd133947dba431f878183602b507a77cd2a0 100644
--- a/corsika/detail/modules/tracking/TrackingStraight.inl
+++ b/corsika/detail/modules/tracking/TrackingStraight.inl
@@ -82,12 +82,11 @@ namespace corsika::tracking_line {
   }
 
   template <typename TParticle>
-  inline Intersections Tracking::intersect(TParticle const& particle,
-                                           Cubic const& cubic) {
+  inline Intersections Tracking::intersect(TParticle const& particle, Box const& box) {
     Point const& position = particle.getPosition();
     VelocityVector const velocity =
         particle.getMomentum() / particle.getEnergy() * constants::c;
-    CoordinateSystemPtr const& cs = cubic.getCoordinateSystem();
+    CoordinateSystemPtr const& cs = box.getCoordinateSystem();
     LengthType x0 = position.getX(cs);
     LengthType y0 = position.getY(cs);
     LengthType z0 = position.getZ(cs);
@@ -95,7 +94,7 @@ namespace corsika::tracking_line {
     SpeedType vy = velocity.getY(cs);
     SpeedType vz = velocity.getZ(cs);
     CORSIKA_LOG_TRACE(
-        "particle in cubic coordinate: position: ({:.3f}, {:.3f}, "
+        "particle in box coordinate: position: ({:.3f}, {:.3f}, "
         "{:.3f}) m, veolocity: ({:.3f}, {:.3f}, {:.3f}) m/ns",
         x0 / 1_m, y0 / 1_m, z0 / 1_m, vx / (1_m / 1_ns), vy / (1_m / 1_ns),
         vz / (1_m / 1_ns));
@@ -109,9 +108,9 @@ namespace corsika::tracking_line {
         return std::make_pair(t2, t1);
     };
 
-    auto [tx_max, tx_min] = get_intersect_min_max(x0, vx, cubic.getX());
-    auto [ty_max, ty_min] = get_intersect_min_max(y0, vy, cubic.getY());
-    auto [tz_max, tz_min] = get_intersect_min_max(z0, vz, cubic.getZ());
+    auto [tx_max, tx_min] = get_intersect_min_max(x0, vx, box.getX());
+    auto [ty_max, ty_min] = get_intersect_min_max(y0, vy, box.getY());
+    auto [tz_max, tz_min] = get_intersect_min_max(z0, vz, box.getZ());
 
     TimeType t_exit = std::min(std::min(tx_max, ty_max), tz_max);
     TimeType t_enter = std::max(std::max(tx_min, ty_min), tz_min);
@@ -119,7 +118,7 @@ namespace corsika::tracking_line {
     CORSIKA_LOG_DEBUG("t_enter: {} ns, t_exit: {} ns", t_enter / 1_ns, t_exit / 1_ns);
     if ((t_exit > t_enter)) {
       if (t_enter < 0_s && t_exit > 0_s)
-        CORSIKA_LOG_DEBUG("numericallyInside={}", cubic.contains(position));
+        CORSIKA_LOG_DEBUG("numericallyInside={}", box.contains(position));
       else if (t_enter < 0_s && t_exit < 0_s)
         CORSIKA_LOG_DEBUG("oppisite direction");
       return Intersections(std::move(t_enter), std::move(t_exit));
@@ -133,9 +132,8 @@ namespace corsika::tracking_line {
     if (Sphere const* sphere = dynamic_cast<Sphere const*>(&volumeNode.getVolume());
         sphere) {
       return Tracking::intersect<TParticle>(particle, *sphere);
-    } else if (Cubic const* cubic = dynamic_cast<Cubic const*>(&volumeNode.getVolume());
-               cubic) {
-      return Tracking::intersect<TParticle>(particle, *cubic);
+    } else if (Box const* box = dynamic_cast<Box const*>(&volumeNode.getVolume()); box) {
+      return Tracking::intersect<TParticle>(particle, *box);
     } else {
       throw std::runtime_error(
           "The Volume type provided is not supported in "
diff --git a/corsika/detail/modules/writers/ObservationCubicWriterParquet.inl b/corsika/detail/modules/writers/ObservationCubicWriterParquet.inl
deleted file mode 100644
index 1a79a4b0d851ee7bb1b507e55bcd1d13d57c1b0c..0000000000000000000000000000000000000000
--- a/corsika/detail/modules/writers/ObservationCubicWriterParquet.inl
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * (c) Copyright 2021 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#pragma once
-
-namespace corsika {
-
-  inline ObservationCubicWriterParquet::ObservationCubicWriterParquet()
-      : output_() {}
-
-  inline void ObservationCubicWriterParquet::startOfLibrary(
-      boost::filesystem::path const& directory) {
-
-    // setup the streamer
-    output_.initStreamer((directory / "particles.parquet").string());
-
-    // enable compression with the default level
-    output_.enableCompression();
-
-    // build the schema
-    output_.addField("pdg", parquet::Repetition::REQUIRED, parquet::Type::INT32,
-                     parquet::ConvertedType::INT_32);
-    output_.addField("energy", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("x", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("y", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("z", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("nx", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("ny", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("nz", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-    output_.addField("t", parquet::Repetition::REQUIRED, parquet::Type::FLOAT,
-                     parquet::ConvertedType::NONE);
-
-    // and build the streamer
-    output_.buildStreamer();
-  }
-
-  inline void ObservationCubicWriterParquet::endOfShower() { ++shower_; }
-
-  inline void ObservationCubicWriterParquet::endOfLibrary() { output_.closeStreamer(); }
-
-  inline void ObservationCubicWriterParquet::write(
-      Code const& pid, HEPEnergyType const& energy, LengthType const& x,
-      LengthType const& y, LengthType const& z, double nx, double ny, double nz,
-      TimeType const& t) {
-    // write the next row - we must write `shower_` first.
-    *(output_.getWriter()) << shower_ << static_cast<int>(get_PDG(pid))
-                           << static_cast<float>(energy / 1_GeV)
-                           << static_cast<float>(x / 1_m) << static_cast<float>(y / 1_m)
-                           << static_cast<float>(z / 1_m) << static_cast<float>(nx)
-                           << static_cast<float>(ny) << static_cast<float>(nz)
-                           << static_cast<float>(t / 1_ns) << parquet::EndRow;
-  }
-
-} // namespace corsika
diff --git a/corsika/framework/geometry/Cubic.hpp b/corsika/framework/geometry/Box.hpp
similarity index 77%
rename from corsika/framework/geometry/Cubic.hpp
rename to corsika/framework/geometry/Box.hpp
index d0aee3f1cd221032ea50ee82cab0db45336f0df6..729e64ebc28ee2ac5ba4967486d60c3bd42777fd 100644
--- a/corsika/framework/geometry/Cubic.hpp
+++ b/corsika/framework/geometry/Box.hpp
@@ -6,19 +6,19 @@
 #include <corsika/framework/geometry/IVolume.hpp>
 
 namespace corsika {
-  class Cubic : public IVolume {
+  class Box : public IVolume {
 
   public:
     // a CoordinateSystemPtr to specify the orintation of coordinate
-    Cubic(Point const& center, CoordinateSystemPtr cs, LengthType const x,
-          LengthType const y, LengthType const z)
+    Box(Point const& center, CoordinateSystemPtr cs, LengthType const x,
+        LengthType const y, LengthType const z)
         : center_(center)
         , cs_(make_translation(cs, center.getCoordinates(cs)))
         , x_(x)
         , y_(y)
         , z_(z) {}
 
-    Cubic(Point const& center, CoordinateSystemPtr cs, LengthType const side)
+    Box(Point const& center, CoordinateSystemPtr cs, LengthType const side)
         : center_(center)
         , cs_(make_translation(cs, center.getCoordinates(cs)))
         , x_(side / 2)
@@ -30,12 +30,16 @@ namespace corsika {
 
     Point const& getCenter() const { return center_; };
     CoordinateSystemPtr const getCoordinateSystem() const { return cs_; }
+
     LengthType const getX() const { return x_; }
     LengthType const getY() const { return y_; }
     LengthType const getZ() const { return z_; }
 
     std::string asString() const;
 
+    template <typename TDim>
+    void rotate(QuantityVector<TDim> const& axis, double const angle);
+
   protected:
     Point center_;
     CoordinateSystemPtr cs_; // local coordinate system with center_ in coordinate (0, 0,
@@ -47,4 +51,4 @@ namespace corsika {
 
 } // namespace corsika
 
-#include <corsika/detail/framework/geometry/Cubic.inl>
+#include <corsika/detail/framework/geometry/Box.inl>
diff --git a/corsika/modules/ObservationCubic.hpp b/corsika/modules/ObservationCubic.hpp
deleted file mode 100644
index f7179bf117f0651126634212340e3ab29d80a55d..0000000000000000000000000000000000000000
--- a/corsika/modules/ObservationCubic.hpp
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma once
-
-#include <corsika/framework/geometry/Cubic.hpp>
-
-#include <corsika/modules/writers/ObservationCubicWriterParquet.hpp>
-#include <corsika/framework/process/ContinuousProcess.hpp>
-
-namespace corsika {
-
-  /**
-     @ingroup Modules
-     @{
-
-     The ObservationCubic writes PDG codes, energies, and distances of particles to the
-     central point of the plane into its output file. The particles are considered
-     "absorbed" afterwards.
-
-     **Note/Limitation:** as discussed in
-     https://gitlab.ikp.kit.edu/AirShowerPhysics/corsika/-/issues/397
-     you cannot put two ObservationCubics exactly on top of each
-     other. Even if one of them is "permeable". You have to put a
-     small gap in between the two plane in such a scenario, or develop
-     another more specialized output class.
-   */
-  template <typename TTracking, typename TOutputWriter = ObservationCubicWriterParquet>
-  class ObservationCubic
-      : public Cubic,
-        public ContinuousProcess<ObservationCubic<TTracking, TOutputWriter>>,
-        public TOutputWriter {
-
-  public:
-    ObservationCubic(Point const& center, CoordinateSystemPtr cs, LengthType const x,
-                     LengthType const y, LengthType const z, bool = true);
-
-    template <typename TParticle, typename TTrajectory>
-    ProcessReturn doContinuous(TParticle& vParticle, TTrajectory& vTrajectory,
-                               bool const stepLimit);
-
-    template <typename TParticle, typename TTrajectory>
-    LengthType getMaxStepLength(TParticle const&, TTrajectory const& vTrajectory);
-
-    void showResults() const;
-    void reset();
-    HEPEnergyType getEnergy() const { return energy_; }
-    YAML::Node getConfig() const;
-
-  private:
-    bool const deleteOnHit_;
-    HEPEnergyType energy_;
-    unsigned int count_;
-  };
-  //! @}
-} // namespace corsika
-
-#include <corsika/detail/modules/ObservationCubic.inl>
\ No newline at end of file
diff --git a/corsika/modules/tracking/TrackingStraight.hpp b/corsika/modules/tracking/TrackingStraight.hpp
index 31b2fad4ffbdf42b268c4f9992aa254135ee05d6..336e4f0bccf1b6d1b2c045ad3298a28ab9575a0a 100644
--- a/corsika/modules/tracking/TrackingStraight.hpp
+++ b/corsika/modules/tracking/TrackingStraight.hpp
@@ -12,7 +12,7 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Plane.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
-#include <corsika/framework/geometry/Cubic.hpp>
+#include <corsika/framework/geometry/Box.hpp>
 #include <corsika/framework/geometry/Vector.hpp>
 #include <corsika/framework/geometry/StraightTrajectory.hpp>
 #include <corsika/framework/geometry/Intersections.hpp>
@@ -52,7 +52,7 @@ namespace corsika::tracking_line {
     static Intersections intersect(TParticle const& particle, Sphere const& sphere);
 
     template <typename TParticle>
-    static Intersections intersect(TParticle const& particle, Cubic const& cubic);
+    static Intersections intersect(TParticle const& particle, Box const& box);
 
     //! find intersection of Volume node with Track of particle
     template <typename TParticle, typename TBaseNodeType>
diff --git a/corsika/modules/writers/ObservationCubicWriterParquet.hpp b/corsika/modules/writers/ObservationCubicWriterParquet.hpp
deleted file mode 100644
index 97e518c8d3da9e9272dce572cf46cbfdc4429d55..0000000000000000000000000000000000000000
--- a/corsika/modules/writers/ObservationCubicWriterParquet.hpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * (c) Copyright 2021 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/output/BaseOutput.hpp>
-#include <corsika/output/ParquetStreamer.hpp>
-#include <corsika/framework/core/ParticleProperties.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
-
-namespace corsika {
-
-  class ObservationCubicWriterParquet : public BaseOutput {
-
-    ParquetStreamer output_; ///< The primary output file.
-
-  public:
-    /**
-     * Construct an ObservationPlane.
-     *
-     * @param name    The name of this output.
-     */
-    ObservationCubicWriterParquet();
-
-    /**
-     * Called at the start of each library.
-     */
-    void startOfLibrary(boost::filesystem::path const& directory) final override;
-
-    /**
-     * Called at the end of each shower.
-     */
-    void endOfShower() final override;
-
-    /**
-     * Called at the end of each library.
-     *
-     * This must also increment the run number since we override
-     * the default behaviour of BaseOutput.
-     */
-    void endOfLibrary() final override;
-
-  protected:
-    /**
-     * Write a particle to the file.
-     */
-    void write(Code const& pid, HEPEnergyType const& energy, LengthType const& x,
-               LengthType const& y, LengthType const& z, double nx, double ny, double nz,
-               TimeType const& t);
-
-  }; // class ObservationCubicWriterParquet
-
-} // namespace corsika
-
-#include <corsika/detail/modules/writers/ObservationCubicWriterParquet.inl>
diff --git a/tests/framework/testGeometry.cpp b/tests/framework/testGeometry.cpp
index 19c311881c728524aa7256464f30d7ed52975d55..67242751c413312db71e6861c2a5aa74f8929336 100644
--- a/tests/framework/testGeometry.cpp
+++ b/tests/framework/testGeometry.cpp
@@ -10,15 +10,16 @@
 
 #include <cmath>
 #include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/geometry/Box.hpp>
 #include <corsika/framework/geometry/CoordinateSystem.hpp>
-#include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Helix.hpp>
-#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/LeapFrogTrajectory.hpp>
+#include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Path.hpp>
+#include <corsika/framework/geometry/Point.hpp>
 #include <corsika/framework/geometry/RootCoordinateSystem.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
 #include <corsika/framework/geometry/StraightTrajectory.hpp>
-#include <corsika/framework/geometry/LeapFrogTrajectory.hpp>
 
 #include <PhysicalUnitsCatch2.hpp> // namespace corsike::testing
 
@@ -282,6 +283,51 @@ TEST_CASE("Geometry Sphere") {
   }
 }
 
+TEST_CASE("Geometry Box") {
+  CoordinateSystemPtr const& rootCS = get_root_CoordinateSystem();
+  Point center(rootCS, {0_m, 0_m, 5_m});
+  Box box(center, rootCS, 4_m, 5_m, 6_m);
+
+  SECTION("getCenter") {
+    CHECK((box.getCenter().getCoordinates(rootCS) - center.getCoordinates())
+              .getNorm()
+              .magnitude() == Approx(0).margin(absMargin));
+    CHECK(box.getX() / 4_m == Approx(1));
+    CHECK(box.getY() / 5_m == Approx(1));
+    CHECK(box.getZ() / 6_m == Approx(1));
+  }
+
+  SECTION("isInside") {
+    CHECK_FALSE(box.contains(Point(rootCS, {4.5_m, 0_m, 0_m})));
+    CHECK(box.contains(Point(rootCS, {0_m, 4.5_m, 0_m})));
+  }
+
+  SECTION("internal coordinate") {
+    CoordinateSystemPtr const internalCS = box.getCoordinateSystem();
+    auto coordinate = center.getCoordinates(internalCS);
+    CHECK(coordinate.getX() / 1_m == Approx(0));
+    CHECK(coordinate.getY() / 1_m == Approx(0));
+    CHECK(coordinate.getZ() / 1_m == Approx(0));
+  }
+
+  SECTION("rotation") {
+    QuantityVector<length_d> const axis_z{0_m, 0_m, 1_m};
+    box.rotate(axis_z, 90);
+    CHECK(box.contains(Point(rootCS, {4.5_m, 0_m, 0_m})));
+    CHECK_FALSE(box.contains(Point(rootCS, {0_m, 4.5_m, 0_m})));
+  }
+
+  SECTION("from different coordinate") {
+    CoordinateSystemPtr const& rootCS = get_root_CoordinateSystem();
+    QuantityVector<length_d> const axis_z{0_m, 0_m, 1_m};
+    auto rotatedCS = make_rotation(rootCS, axis_z, 90);
+    Point center(rootCS, {0_m, 0_m, 5_m});
+    Box box(center, rotatedCS, 4_m, 5_m, 6_m);
+    CHECK(box.contains(Point(rootCS, {4.5_m, 0_m, 0_m})));
+    CHECK_FALSE(box.contains(Point(rootCS, {0_m, 4.5_m, 0_m})));
+  }
+}
+
 TEST_CASE("Geometry Trajectories") {
   CoordinateSystemPtr rootCS = get_root_CoordinateSystem();
   Point r0(rootCS, {0_m, 0_m, 0_m});