From 9171fb7dc88c5e76947769702c81aae55397f6cb Mon Sep 17 00:00:00 2001 From: lorber98 <95907752+lorber98@users.noreply.github.com> Date: Tue, 20 Oct 2026 20:51:15 +0200 Subject: [PATCH 1/4] [ALICE3] TRK: rInnRow column for staggered ML layers in the config file The TRK configuration file (TRKBase.configFile) accepts an optional 8th column, rInnRow, which chooses the staves of a staggered layer (layers 3 and 4) that sit at rInn: 0 = even staves (default, previous behaviour), 1 = odd staves, i.e. stave 0 in the shifted row. Existing files and the default layouts give the same geometry as before. A value other than 0 or 1 is fatal. Co-Authored-By: Claude Opus 5.5 --- Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md | 3 ++- .../include/TRKSimulation/TRKLayer.h | 3 ++- .../TRKFT3/TRK/simulation/src/Detector.cxx | 5 ++++- .../TRKFT3/TRK/simulation/src/TRKLayer.cxx | 18 +++++++++++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md index 71bbce74dcbb4..3bd2651fdc5e5 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md @@ -68,7 +68,8 @@ Both layouts use the same configuration-file syntax (only the OT geometry implem **Middle Layers (ML) - Indices 0 to 4** The first 5 valid lines are parsed as `TRKMLLayer` objects. These layers **require** a 6th parameter for the staggering offset (`stagOffset`). -* **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `stagOffset` \t `[optional_mode]` +* **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `stagOffset` \t `[optional_mode]` \t `[optional_rInnRow]` +* Layers 3 and 4 are staggered: one stave out of two sits at `rInn`, the others are shifted by `stagOffset`. By default the even staves (stave 0 at phi = 0) are at `rInn`; `rInnRow` = 1 puts the odd staves there instead, i.e. stave 0 in the shifted row. In the flipped layer 3 the `rInn` row is the outer one and the shift points inwards, so there `rInnRow` = 1 puts stave 0 in the inner row; in layer 4 in the outer row. To give `rInnRow`, the mode column must be written too. **Outer Layers (OT) - Indices 5 and above** From the 6th valid line onwards, lines are parsed as OT layer objects (`TRKOTLayer` for `kSegmented`, `TRKOTLayerRealistic` for `kSimplifiedRealistic`). These layers do **not** have a staggering offset. The optional mode parameter shifts to the 6th column. diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h index a533bd1066b41..e6603c791b9de 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -111,7 +111,7 @@ class TRKMLLayer : public TRKSegmentedLayer { public: TRKMLLayer() = default; - TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode, int rInnRow = 0); ~TRKMLLayer() override = default; TGeoVolume* createStave() override; @@ -121,6 +121,7 @@ class TRKMLLayer : public TRKSegmentedLayer TGeoVolume* createEndOfStaveCard(); // A-side (+z) end-of-stave card, in front of the conn disk float mStaggerOffset; + int mRInnRow = 0; // staggered layers: staves with iStave % 2 == mRInnRow sit at rInn, the others are shifted by the stagger offset static constexpr double sStaveWidth = constants::ML::width; static constexpr int sFlippedLayerNumber = 3; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx index bd34c9109f72b..e0aa44a17127b 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx @@ -225,6 +225,8 @@ void Detector::configFromFile(std::string fileName) // tmpBuff[4] = nMods // tmpBuff[5] = stagOffset (required ONLY for ML) // tmpBuff[6] = matBudgetMode (optional, default = Thickness) + // tmpBuff[7] = rInnRow (optional, ML only, default = 0): which staves of a staggered layer sit at + // rInn, 0 = even, 1 = odd (needs tmpBuff[6]); in the flipped layer the rInn row is the outer one // Base parameters for all segmented layers (at least 5 needed) if (tmpBuff.size() < 5) { @@ -250,8 +252,9 @@ void Detector::configFromFile(std::string fileName) if (tmpBuff.size() >= 7) { matBudgetMode = static_cast(static_cast(tmpBuff[6])); } + const int rInnRow = (tmpBuff.size() >= 8) ? static_cast(tmpBuff[7]) : 0; - mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, matBudgetMode)); + mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, matBudgetMode, rInnRow)); } else { // OT layers do NOT have stagOffset. The optional mode is at index 5. if (tmpBuff.size() >= 6) { diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx index eb33616f8e0db..19618978f0b6a 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx @@ -233,8 +233,8 @@ std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) c ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode) - : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset) +TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode, int rInnRow) + : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset), mRInnRow(rInnRow) { if (mLayerNumber == sFlippedLayerNumber) { mOuterRadius = rInn; @@ -243,6 +243,18 @@ TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float mStaggerOffset = -staggerOffset; LOGP(info, "Layer {} is flipped: sensor and metal stack positions are switched", mLayerNumber); } + if (mRInnRow != 0 && mRInnRow != 1) { + LOGP(fatal, "Layer {}: rInnRow = {}, use 0 (even staves at rInn) or 1 (odd staves at rInn)", mLayerNumber, mRInnRow); + } + if (mRInnRow != 0) { + if (mLayerNumber == 3 || mLayerNumber == 4) { + // even staves (stave 0) go to the shifted row: inside the rInn row in the flipped layer, outside + // otherwise (stagOffset >= 0; a negative stagOffset is not handled by getBoundingRadii) + LOGP(info, "Layer {} (staggered): even staves in the {} row (rInnRow = {})", mLayerNumber, mIsFlipped ? "inner" : "outer", mRInnRow); + } else { + LOGP(info, "Layer {} is not staggered: rInnRow = {} has no effect", mLayerNumber, mRInnRow); + } + } } TGeoVolume* TRKMLLayer::createStave() @@ -342,7 +354,7 @@ void TRKMLLayer::createLayer(TGeoVolume* motherVolume) TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); trans->SetRotation(rot); // float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == 0 ? mInnerRadius : mInnerRadius + mStaggerOffset) : mInnerRadius; - float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == 0 ? avgRadiusInner : avgRadiusOuter) : avgRadiusInner; + float trueRadius = (mLayerNumber == 3 || mLayerNumber == 4) ? (iStave % 2 == mRInnRow ? avgRadiusInner : avgRadiusOuter) : avgRadiusInner; trans->SetTranslation(trueRadius * std::cos(phi), trueRadius * std::sin(phi), 0); LOGP(debug, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); layerVol->AddNode(staveVol, iStave, trans); From 4913f2c5550c42e6dc6b65b65c7a630a253b5063 Mon Sep 17 00:00:00 2001 From: lorber98 <95907752+lorber98@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:17:30 +0200 Subject: [PATCH 2/4] [ALICE3] TRK: phiOffset column for the ML layers in the config file The TRK configuration file (TRKBase.configFile) accepts an optional 9th column, phiOffset (degrees, counterclockwise as phi), which rotates all the staves of a ML layer about the beam axis, keeping their tilt. Existing files and the default layouts give the same geometry as before. Co-Authored-By: Claude Opus 5.5 --- Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md | 3 ++- .../TRK/simulation/include/TRKSimulation/TRKLayer.h | 3 ++- .../ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx | 5 ++++- .../ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx | 12 ++++++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md index 3bd2651fdc5e5..e95f99c65ad4d 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md @@ -68,8 +68,9 @@ Both layouts use the same configuration-file syntax (only the OT geometry implem **Middle Layers (ML) - Indices 0 to 4** The first 5 valid lines are parsed as `TRKMLLayer` objects. These layers **require** a 6th parameter for the staggering offset (`stagOffset`). -* **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `stagOffset` \t `[optional_mode]` \t `[optional_rInnRow]` +* **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `stagOffset` \t `[optional_mode]` \t `[optional_rInnRow]` \t `[optional_phiOffset]` * Layers 3 and 4 are staggered: one stave out of two sits at `rInn`, the others are shifted by `stagOffset`. By default the even staves (stave 0 at phi = 0) are at `rInn`; `rInnRow` = 1 puts the odd staves there instead, i.e. stave 0 in the shifted row. In the flipped layer 3 the `rInn` row is the outer one and the shift points inwards, so there `rInnRow` = 1 puts stave 0 in the inner row; in layer 4 in the outer row. To give `rInnRow`, the mode column must be written too. +* Stave `s` of a layer with `nStaves` = N is centred at phi = 360° · s / N (+ half a pitch if N is a multiple of 4), with stave 0 at phi = 0. `phiOffset` (degrees, counterclockwise as phi, default 0) rotates the whole layer about the beam axis: every stave is moved to phi + `phiOffset` and keeps its tilt with respect to its own radial direction. To give `phiOffset`, the mode and `rInnRow` columns must be written too. **Outer Layers (OT) - Indices 5 and above** From the 6th valid line onwards, lines are parsed as OT layer objects (`TRKOTLayer` for `kSegmented`, `TRKOTLayerRealistic` for `kSimplifiedRealistic`). These layers do **not** have a staggering offset. The optional mode parameter shifts to the 6th column. diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h index e6603c791b9de..71f9870d86ba5 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -111,7 +111,7 @@ class TRKMLLayer : public TRKSegmentedLayer { public: TRKMLLayer() = default; - TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode, int rInnRow = 0); + TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode, int rInnRow = 0, float phiOffset = 0.f); ~TRKMLLayer() override = default; TGeoVolume* createStave() override; @@ -122,6 +122,7 @@ class TRKMLLayer : public TRKSegmentedLayer float mStaggerOffset; int mRInnRow = 0; // staggered layers: staves with iStave % 2 == mRInnRow sit at rInn, the others are shifted by the stagger offset + float mPhiOffset = 0.f; // deg, rotation of all the staves of the layer about the beam axis (counterclockwise, as phi) static constexpr double sStaveWidth = constants::ML::width; static constexpr int sFlippedLayerNumber = 3; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx index e0aa44a17127b..f5d7c1c0c5cb9 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx @@ -227,6 +227,8 @@ void Detector::configFromFile(std::string fileName) // tmpBuff[6] = matBudgetMode (optional, default = Thickness) // tmpBuff[7] = rInnRow (optional, ML only, default = 0): which staves of a staggered layer sit at // rInn, 0 = even, 1 = odd (needs tmpBuff[6]); in the flipped layer the rInn row is the outer one + // tmpBuff[8] = phiOffset (optional, ML only, default = 0): rotation of all the staves of the layer about + // the beam axis, in degrees, counterclockwise as phi (needs tmpBuff[6] and tmpBuff[7]) // Base parameters for all segmented layers (at least 5 needed) if (tmpBuff.size() < 5) { @@ -253,8 +255,9 @@ void Detector::configFromFile(std::string fileName) matBudgetMode = static_cast(static_cast(tmpBuff[6])); } const int rInnRow = (tmpBuff.size() >= 8) ? static_cast(tmpBuff[7]) : 0; + const float phiOffset = (tmpBuff.size() >= 9) ? tmpBuff[8] : 0.f; - mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, matBudgetMode, rInnRow)); + mLayers.push_back(std::make_unique(layerCount, name, rInn, stagOffset, tiltAngle, nStaves, nMods, thick, matBudgetMode, rInnRow, phiOffset)); } else { // OT layers do NOT have stagOffset. The optional mode is at index 5. if (tmpBuff.size() >= 6) { diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx index 19618978f0b6a..7f1d06cc052cc 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx @@ -233,9 +233,12 @@ std::pair TRKSegmentedLayer::getBoundingRadii(double staveWidth) c ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode, int rInnRow) - : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset), mRInnRow(rInnRow) +TRKMLLayer::TRKMLLayer(int layerNumber, std::string layerName, float rInn, float staggerOffset, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode, int rInnRow, float phiOffset) + : TRKSegmentedLayer(layerNumber, layerName, rInn, tiltAngle, numberOfStaves, numberOfModules, thickOrX2X0, mode), mStaggerOffset(staggerOffset), mRInnRow(rInnRow), mPhiOffset(phiOffset) { + if (mPhiOffset != 0.f) { + LOGP(info, "Layer {}: staves rotated by {} deg about the beam axis (phiOffset)", mLayerNumber, mPhiOffset); + } if (mLayerNumber == sFlippedLayerNumber) { mOuterRadius = rInn; mInnerRadius = rInn - mChipThickness; @@ -348,8 +351,9 @@ void TRKMLLayer::createLayer(TGeoVolume* motherVolume) for (int iStave = 0; iStave < mNumberOfStaves; iStave++) { TGeoVolume* staveVol = createStave(); TGeoCombiTrans* trans = new TGeoCombiTrans(); - // If the number of staves is a multiple of 4, rotate by half a stave to avoid having the first one exactly on the x - double phi = (mNumberOfStaves % 4 == 0) ? theta * (iStave + 0.5) : theta * iStave; + // If the number of staves is a multiple of 4, rotate by half a stave to avoid having the first one exactly on the x; + // mPhiOffset rotates the whole layer (position and orientation of every stave) about the beam axis + double phi = ((mNumberOfStaves % 4 == 0) ? theta * (iStave + 0.5) : theta * iStave) + mPhiOffset * TMath::DegToRad(); double phiDeg = phi * TMath::RadToDeg(); TGeoRotation* rot = new TGeoRotation("rot", phiDeg + 90 + mTiltAngle, 0, 0); trans->SetRotation(rot); From e49ebc6cec288095745fcb9fc5669c063b2bf5a8 Mon Sep 17 00:00:00 2001 From: lorber98 <95907752+lorber98@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:23:43 +0200 Subject: [PATCH 3/4] Fix formatting --- .../TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h index 71f9870d86ba5..4875207ea7397 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -121,7 +121,7 @@ class TRKMLLayer : public TRKSegmentedLayer TGeoVolume* createEndOfStaveCard(); // A-side (+z) end-of-stave card, in front of the conn disk float mStaggerOffset; - int mRInnRow = 0; // staggered layers: staves with iStave % 2 == mRInnRow sit at rInn, the others are shifted by the stagger offset + int mRInnRow = 0; // staggered layers: staves with iStave % 2 == mRInnRow sit at rInn, the others are shifted by the stagger offset float mPhiOffset = 0.f; // deg, rotation of all the staves of the layer about the beam axis (counterclockwise, as phi) static constexpr double sStaveWidth = constants::ML::width; From a2fc897cbfd050e6ef1195c7c29540fb63136431 Mon Sep 17 00:00:00 2001 From: lorber98 <95907752+lorber98@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:58:52 +0200 Subject: [PATCH 4/4] Update readme --- Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md index e95f99c65ad4d..a1d89292be1d5 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md @@ -69,8 +69,8 @@ Both layouts use the same configuration-file syntax (only the OT geometry implem **Middle Layers (ML) - Indices 0 to 4** The first 5 valid lines are parsed as `TRKMLLayer` objects. These layers **require** a 6th parameter for the staggering offset (`stagOffset`). * **Format:** `rInn` \t `thick` \t `tiltAngle` \t `nStaves` \t `nMods` \t `stagOffset` \t `[optional_mode]` \t `[optional_rInnRow]` \t `[optional_phiOffset]` -* Layers 3 and 4 are staggered: one stave out of two sits at `rInn`, the others are shifted by `stagOffset`. By default the even staves (stave 0 at phi = 0) are at `rInn`; `rInnRow` = 1 puts the odd staves there instead, i.e. stave 0 in the shifted row. In the flipped layer 3 the `rInn` row is the outer one and the shift points inwards, so there `rInnRow` = 1 puts stave 0 in the inner row; in layer 4 in the outer row. To give `rInnRow`, the mode column must be written too. -* Stave `s` of a layer with `nStaves` = N is centred at phi = 360° · s / N (+ half a pitch if N is a multiple of 4), with stave 0 at phi = 0. `phiOffset` (degrees, counterclockwise as phi, default 0) rotates the whole layer about the beam axis: every stave is moved to phi + `phiOffset` and keeps its tilt with respect to its own radial direction. To give `phiOffset`, the mode and `rInnRow` columns must be written too. +* Layers 3 and 4 are staggered: one stave out of two sits at `rInn`, the others are shifted by `stagOffset`. By default the even staves are at `rInn`; `rInnRow` = 1 puts the odd staves there instead, i.e. stave 0 in the shifted row. In the flipped layer 3 the `rInn` row is the outer one and the shift points inwards, so there `rInnRow` = 1 puts stave 0 in the inner row; in layer 4 in the outer row. To give `rInnRow`, the mode column must be written too. +* `phiOffset` (degrees, counterclockwise, default 0) rotates the whole layer about the beam axis; each stave keeps its tilt. It requires the mode and `rInnRow` columns. **Outer Layers (OT) - Indices 5 and above** From the 6th valid line onwards, lines are parsed as OT layer objects (`TRKOTLayer` for `kSegmented`, `TRKOTLayerRealistic` for `kSimplifiedRealistic`). These layers do **not** have a staggering offset. The optional mode parameter shifts to the 6th column.