@@ -132,7 +132,48 @@ class TPCVDriftManager
132132
133133 // impose new Z coordinate
134134 const auto sides = (trackExtra.flags () & (o2::aod::track::TrackFlags::TPCSideA | o2::aod::track::TrackFlags::TPCSideC));
135- track.setZ (track.getZ () + (sides == o2::aod::track::TrackFlags::TPCSideC ? -dDrift : (sides == o2::aod::track::TrackFlags::TPCSideA ? dDrift : 0 )));
135+ float zShift = 0 .f ;
136+ if (sides == o2::aod::track::TrackFlags::TPCSideA) {
137+ zShift = dDrift;
138+ } else if (sides == o2::aod::track::TrackFlags::TPCSideC) {
139+ zShift = -dDrift;
140+ } else if (sides == 0 ) {
141+ // Fallback for datasets produced before the TPC side flags were introduced (Feb. 2026).
142+ o2::aod::track::extensions::TPCTimeErrEncoding tEnc;
143+ tEnc.encoding .timeErr = trackExtra.trackTimeRes ();
144+ const float dFwd = tEnc.getDeltaTFwd ();
145+ const float dBwd = tEnc.getDeltaTBwd ();
146+ // Equal, small forward/backward margins mean the track is bounded on both ends,
147+ // i.e. it crosses the CE: it cannot be moved and is already corrected elsewhere.
148+ const bool crossesCE = (dFwd == dBwd) && (dFwd < mMaxCECrossingDeltaTNS );
149+ if (!crossesCE) {
150+ const bool zPositive = track.getZ () > 0 .f ;
151+ const bool tglPositive = track.getTgl () > 0 .f ;
152+ int side = 0 ; // +1 = A, -1 = C, 0 = undetermined -> leave uncorrected
153+ if (zPositive == tglPositive) {
154+ // Consistent sign: the track converges to Z=0 at the beamline by construction.
155+ side = tglPositive ? 1 : -1 ;
156+ } else if (dBwd == 0 .f && dFwd > 0 .f ) {
157+ // Bounded backward at the CE with room forward: no clusters on the opposite
158+ // side, so trust the measured Z rather than the (here inverted) tgl.
159+ side = zPositive ? 1 : -1 ;
160+ } else if (dBwd > 0 .f ) {
161+ // Large tgl track bounded at the readout side instead: trust tgl.
162+ side = tglPositive ? 1 : -1 ;
163+ }
164+ // else: degenerate case, track touches both CE and readout -> cannot be deduced/moved.
165+ // (in practice unreachable here: dFwd==dBwd==0 would already satisfy crossesCE above,
166+ // since dFwd/dBwd are always >= 0; kept explicit to mirror the reference logic 1:1.)
167+
168+ if (side > 0 ) {
169+ zShift = dDrift;
170+ } else if (side < 0 ) {
171+ zShift = -dDrift;
172+ }
173+ }
174+ }
175+ // else: track has clusters on both sides (crossed the CE) and is already corrected elsewhere
176+ track.setZ (track.getZ () + zShift);
136177 if constexpr (std::is_base_of_v<o2::track::TrackParCov, Track>) {
137178 track.setCov (track.getSigmaZ2 () + dDriftErr * dDriftErr, o2::track::kSigZ2 );
138179 }
@@ -157,7 +198,8 @@ class TPCVDriftManager
157198 o2::ccdb::BasicCCDBManager* mCCDB {}; // reference to initialized ccdb manager
158199
159200 static constexpr unsigned int mWarningLimit {10 };
160- static constexpr float mMaxDriftCm {250 .f }; // TPC drift volume half-length in cm
201+ static constexpr float mMaxDriftCm {250 .f }; // TPC drift volume half-length in cm
202+ static constexpr float mMaxCECrossingDeltaTNS {1000 .f }; // ~1 us, ballpark forward/backward time margin of a CE-crossing track
161203
162204 // Counters
163205 unsigned int mCalls {0 }; // total number of calls
0 commit comments