Skip to content

Conversation

@blackcathj
Copy link

@blackcathj blackcathj commented Jan 19, 2026

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work for users)
  • Requiring change in macros repository (Please provide links to the macros pull request in the last section)
  • I am a member of GitHub organization of sPHENIX Collaboration, EIC, or ECCE (contact Chris Pinkenburg to join)

What kind of change does this PR introduce? (Bug fix, feature, ...)

TODOs (if applicable)

Links to other PRs in macros and calibration repositories (if applicable)

Summary by CodeRabbit

  • New Features

    • Added configurable particle trigger system for event filtering based on kinematic cuts
    • Added state-cluster residuals quality assurance module for tracking validation
    • Added script execution hooks before file opening for preprocessing workflows
  • Improvements

    • Enhanced calorimeter QA energy accumulation and trigger gating logic
    • Improved micromegas cluster filtering with configurable sample ranges
    • Refined event handling and error recovery during data processing
    • Enhanced track seed pruning capabilities

✏️ Tip: You can customize this high-level summary in your review settings.

Daniel J Lis and others added 30 commits December 17, 2025 20:59
Fill ntuple for both matched and unmatched waveforms
…s_pool_evaluation_update

Small update on TPOT event combining evaluation
if multiple hits are found on the same strip only the first one, timewise, is kept.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
add quality flag to total energy calc and correlations
pinkenburg and others added 22 commits January 15, 2026 14:40
abort events with any mbd packet empty
New QA module to make cluster-state residual plots
Copilot AI review requested due to automatic review settings January 19, 2026 17:07
@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

The pull request spans multiple detector subsystems with diverse enhancements: introduces a new HepMC particle-based trigger class with configurable kinematic cuts, modernizes codebase with C++20 features (std::format), enhances stream state preservation during framework initialization, extends micromegas hit key tracking to include sample indices, adds new state-cluster residuals QA module, improves calorimeter QA gating logic, and refactors legacy code with const-correctness and contemporary C++ idioms throughout TPC, MVTX, and framework components.

Changes

Cohort / File(s) Summary
HepMC Trigger Enhancements
generators/Herwig/HepMCTrigger/HepMCJetTrigger.cc, generators/Herwig/HepMCTrigger/HepMCParticleTrigger.* (new), generators/Herwig/HepMCTrigger/Makefile.am
Added particle ID filtering (pdg_id range [12,18]) in jet input and eta gating (abs(eta) > 1.1) before pT thresholding. Introduced new HepMCParticleTrigger class with configurable per-particle kinematic cuts (Pt, P, Pz, Eta, AbsEta), per-particle counting, and stable-particle-only mode. New library and headers added to build system.
Pythia8 and HepMC Integration
generators/PHPythia8/PHPythia8.cc, generators/phhepmc/PHHepMCGenEventv1.cc
Added environment-based Pythia8 path initialization with error handling. Inserted cout formatting save/restore guards around Pythia initialization, construction, and event processing to preserve stream state. Changed flow psi NaN return from 0.0 to std::numeric_limits::quiet_NaN() and added <limits> include.
Calorimeter QA Updates
offline/QA/Calorimeters/CaloValid.cc, offline/QA/Calorimeters/CaloValid.h
Modified energy accumulation to only increment totals when towers pass isGood checks. Changed trigger bit gating from scaledBits[11] to scaledBits[12] for CEMC/IHCAL/OHCAL. Added trigger index 12 to triggerIndices vector.
Tracking QA and Micromegas
offline/QA/Tracking/StateClusterResidualsQA.* (new), offline/QA/Tracking/MicromegasClusterQA.*, offline/QA/Tracking/Makefile.am
New StateClusterResidualsQA module with configurable residual histograms, builder-style configuration API, and track-state-to-cluster residual computation. Enhanced MicromegasClusterQA with sample range filtering (m_sample_min/max) and associated setters. Updated Makefile to build new QA module.
Framework: Module Cleanup
offline/framework/ffamodules/CDBInterface.*, offline/framework/ffamodules/FlagHandler.h, offline/framework/ffamodules/HeadReco.*, offline/framework/ffamodules/SyncReco.h, offline/framework/ffamodules/Timing.*
Removed PHCompositeNode forward declarations from headers (compilation risk). Converted empty destructors to = default syntax. Changed parameter naming to suppress unused warnings. Added conditional insertion guards for empty URLs in CDBInterface. Added flow psi map emptiness check in HeadReco. Updated member initializer syntax in SyncReco. Removed include from Timing.cc.
Framework: Fun4All Core
offline/framework/fun4all/Fun4AllServer.*, offline/framework/fun4all/InputFileHandler.*
Added cout state save/restore mechanism in Fun4AllServer (m_saved_cout_state member with duplicate declaration in header). Introduced pre-opening file script hooks in InputFileHandler: SetOpeningScript, GetOpeningScript, SetOpeningScriptArgs, GetOpeningScriptArgs, RunBeforeOpening methods with validation and execution via gSystem->Exec.
Framework: Raw Data Input
offline/framework/fun4allraw/Fun4AllStreamingInputManager.cc, offline/framework/fun4allraw/SingleMicromegasPoolInput_v2.*, offline/framework/fun4allraw/SingleMvtxPoolInput.cc, offline/framework/fun4allraw/SingleTriggeredInput.*
Refactored raw hit map processing from while-erase loops to for-range with early breaks, reducing map mutations. Added matched boolean to Waveform struct for QA evaluation. Modified MVTX m_NegativeBco from 500 to 120. Introduced skip-trace retry loop (up to 5 iterations) for GL1 clock reconciliation with detailed diagnostics. Removed EventsInThisFile accessor; added eventcounter member.
Calo Embedding
offline/packages/CaloEmbedding/CombineTowerInfo.* (new), offline/packages/CaloEmbedding/CopyIODataNodes.*, offline/packages/CaloEmbedding/Makefile.am
New CombineTowerInfo module aggregates two TowerInfoContainer inputs into one, summing energies. Extended CopyIODataNodes with TowerInfo copying capability via set_CopyTowerInfo setter and CreateTowerInfo/CopyTowerInfo helpers. Updated Makefile to export new headers and sources.
Micromegas Hit Tracking
offline/packages/micromegas/MicromegasCombinedDataDecoder.*, offline/packages/micromegas/MicromegasDefs.*
Extended hit key tracking from strip-only to strip+sample pairs. genHitKey now accepts sample parameter; getStrip return type changed uint16_t→uint8_t; added getSample accessor. Combined ADC samples stored as pairs in adc_list; max_adc selection uses pair comparator. Hit creation and logging updated to track sample indices and count signal hits.
MVTX and TPC Modernization
offline/packages/mvtx/CylinderGeom_Mvtx.*, offline/packages/mvtx/MvtxClusterPruner.cc, offline/packages/mvtx/MvtxClusterizer.*, offline/packages/mvtx/MvtxHitPruner.cc, offline/packages/mvtx/SegmentationAlpide.cc
Added const-qualifiers to pixel-coordinate and adjacency-check methods. Updated destructors to = default. Changed constructor parameter in_Nstaves→in_N_staves. Replaced Boost.Format with std::format. Applied pointer-type explicit declarations (auto→auto*), early null checks, and range_adaptor constructor usage. Minor formatting and style consistency improvements.
TPC Clusterization Refactoring
offline/packages/tpc/LaserClusterizer.cc, offline/packages/tpc/Tpc3DClusterizer.cc, offline/packages/tpc/TpcClusterizer.cc, offline/packages/tpc/TpcClusterMover.*, offline/packages/tpc/TpcCombinedRawDataUnpacker*.cc, offline/packages/tpc/TpcDistortionCorrection.cc, offline/packages/tpc/TpcLoadDistortionCorrection.cc, offline/packages/tpc/TpcRawDataTree.cc, offline/packages/tpc/TpcRawWriter.cc, offline/packages/tpc/TpcSimpleClusterizer.cc, offline/packages/tpc/TrainingHits.cc
Replaced std::min/std::max inline logic with standard library calls. Converted auto to explicit auto* for pointer variables. Replaced container.size()>0 with container.empty(). Consolidated boundary clamping with std::max/std::min. Added include <algorithm> and <format>. Moved member initializations from constructor body to initializer lists. Replaced C-style casts with static_cast. Used std::vector::data() for branch setup. Added const-qualifier to TpcClusterMover::get_circle_circle_intersection. Minor formatting for readability.
Track Reconstruction
offline/packages/PHGenFitPkg/PHGenFit/Fitter.cc, offline/packages/PHGenFitPkg/PHGenFit/Track.cc, offline/packages/trackreco/DSTClusterPruning.*, offline/packages/trackreco/PHActsTrkFitter.*, offline/packages/trackreco/PHSiliconTpcTrackMatching.*, offline/packages/trackreco/PHSimpleVertexFinder.h, offline/packages/trackreco/PHTpcDeltaZCorrection.h
Replaced string.compare()>0 with direct == operator. Moved TGeoManager initialization to initializer list. Added early-exit conditions for clusters on seeds in DSTClusterPruning (m_pruneAllSeeds flag). Made setTrkrClusterContainerName parameter const-qualified across multiple files. Added _cluster_map_name configurable in PHSiliconTpcTrackMatching. Minor formatting and pointer-type standardization.
Physics Triggers and Background
offline/packages/Skimmers/Trigger/TriggerDSTSkimmer.*, offline/packages/jetbackground/DetermineTowerBackground.*, offline/packages/jetbackground/Makefile.am, offline/packages/CaloReco/PhotonClusterBuilder.cc
Added max-event acceptance limit feature to TriggerDSTSkimmer with set_accept_max setter. Changed GL1Packet lookup from class name to numeric ID 14001. Added LoadCalibrations method to DetermineTowerBackground for centrality-based v2 handling; introduced new do_flow==4 branch. Updated Makefile with centrality_io, cdbobjects, ffamodules dependencies. Added shower shape parameter e22 to PhotonClusterBuilder.
MBD and Event Processing
offline/packages/mbd/MbdCalib.cc, offline/packages/mbd/MbdEvent.cc, offline/packages/mbd/MbdReco.*, offline/packages/mbd/MbdSig.h, offline/packages/trackbase/AlignmentTransformation.*
Changed TimeRMS missing-data log from ERROR to WARNING. Added early-skip guards for empty sample counts in MbdEvent. Added MbdReco event number tracking (_evtnum). Exposed GetNSamples public accessor in MbdSig. Added use_module_tilt flag to AlignmentTransformation with conditional transform composition logic.
File Processing
offline/framework/frog/CreateFileList.pl
Added production type 37 for Hijing O+O collisions at 0-15 fm with corresponding filenamestring and proddesc entries.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The review spans heterogeneous changes across 70+ files with mixed complexity: new particle-based trigger class with kinematic filtering logic, modernized C++ patterns throughout, const-correctness additions, new QA module with configurable residuals, stream state preservation mechanisms, forward declaration removals (compilation risk), refactored raw-data processing loops, extended hit key representation, and framework hook additions. While many individual changes follow consistent patterns (formatting, const-correctness, auto→auto*), each cohort requires separate reasoning. The duplicate member declaration in Fun4AllServer.h is a potential blocker requiring clarification. High disparity between file count, change heterogeneity, and logic density.

Poem

🐰 New triggers catch particles with care,
Stream states preserved through the air,
Const-correctness blooms with flair,
Clusters combine, histograms share,
Old patterns fade—modern code's fair! ✨

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the repository's template with unchecked change-type boxes and no implementation-specific details, rationale, or links to related changes. Complete the description by selecting applicable change types, explaining what was changed and why, and providing links to related PRs or discussions if applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Test2' is vague and generic, providing no meaningful information about the changeset's primary purpose or scope. Replace the title with a clear, concise summary of the main change or feature. For example, describe the most significant modification across the large changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Docstrings were successfully generated.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request contains a large set of code modernization, refactoring, and feature additions across the offline reconstruction and analysis framework. The changes include code style improvements (const-correctness, auto* usage, formatting), algorithmic optimizations (std::min/max), new analysis modules, and bug fixes.

Changes:

  • Code style modernization: const-correctness for string parameters, auto* for pointers, consistent formatting
  • Performance improvements: replacing manual comparisons with std::min/std::max
  • New features: flow calibration system for jet background, tower combination utility, state-cluster residuals QA
  • Bug fixes: micromegas hit key sample tracking, MVTX/INTT pool filling logic, MBD empty packet handling

Reviewed changes

Copilot reviewed 87 out of 87 changed files in this pull request and generated no comments.

Show a summary per file
File Description
PHTpcDeltaZCorrection.h, PHSimpleVertexFinder.h, PHActsTrkFitter.h, PHActsTrkFitter.cc Added const to string parameter references for setters
PHSiliconTpcTrackMatching.h/.cc Added cluster map name configurability
DSTClusterPruning.h/.cc Added pruneAllSeeds feature
AlignmentTransformation.h/.cc Enhanced TPC module tilt handling and transform logic
TrainingHits.cc Moved member initializations to initializer list
TpcSimpleClusterizer.cc, TpcClusterizer.cc, etc. Replaced manual min/max with std::min/std::max, changed auto to auto*
TpcRawDataTree.cc Changed array access to .data() method
TpcDistortionCorrection.cc Removed inline keyword from constexpr
LaserClusterizer.cc Major formatting improvements and code cleanup
SegmentationAlpide.cc Replaced boost::format with std::format
MvtxHitPruner.cc, MvtxClusterizer.cc Formatting and const-correctness improvements
CylinderGeom_Mvtx.h/.cc Added const to methods, removed using namespace std
MicromegasDefs.h/.cc Added sample field to hit keys
MicromegasCombinedDataDecoder.cc Enhanced to track sample with maximum ADC
MicromegasClusterizer.cc Fixed duplicate hit handling by sample
MbdReco.cc, MbdEvent.cc, MbdCalib.cc Added event number to warnings, added empty packet checks
DetermineTowerBackground.h/.cc Added flow v2 calibration system
TriggerDSTSkimmer.h/.cc Added max event acceptance feature
KFParticle_sPHENIX.cc Changed ABORTEVENT to EVENT_OK for missing tracks/vertices
PhotonClusterBuilder.cc Added e22 shower shape parameter
CombineTowerInfo.h/.cc New utility for combining tower information
CopyIODataNodes.h/.cc Added TowerInfo copying capability
SingleTriggeredInput.h/.cc Enhanced GL1 skip logic and BCO alignment
SingleMvtxPoolInput.cc Adjusted negative BCO handling
SingleMicromegasPoolInput_v2.h/.cc Added matched flag to waveform structure
Fun4AllStreamingInputManager.cc Refactored INTT and MVTX filling logic
InputFileHandler.h/.cc Added script execution before file opening
Fun4AllServer.h/.cc Added cout state restoration
CreateFileList.pl Added O+O hijing production type
Timing.h/.cc, SyncReco.h, HeadReco.h/.cc, FlagHandler.h, CDBInterface.h/.cc Minor cleanups
StateClusterResidualsQA.h/.cc New QA module for track state-cluster residuals
MicromegasClusterQA.h/.cc Added sample range filtering
CaloValid.h Added trigger index 12
PHHepMCGenEventv1.cc Return NaN instead of 0 for missing flow psi
PHGenFit Track.cc, Fitter.cc Code cleanup and style improvements

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
offline/framework/fun4allraw/SingleMvtxPoolInput.cc (1)

210-229: Guard against empty m_BclkStack before dereferencing it

When m_BclkStack is empty, lower_bound returns begin()==end() and Line 214 dereferences *it, which is UB and can crash. Add an early empty check before dereferencing.

🐛 Proposed fix
-    auto strb_it = m_BclkStack.cend(); 
+    if (m_BclkStack.empty())
+    {
+      continue;
+    }
+    auto strb_it = m_BclkStack.cend();
 
     if (it == m_BclkStack.begin())
     {
       if (*it == lv1Bco)
       {
         strb_it = it;
       }
       else
       {
         strb_it = m_BclkStack.cend();
       }
     }
     else
     {
       // safe because it != begin()
       auto prev = it;
       --prev;
       strb_it = prev;
     }
offline/framework/fun4allraw/SingleMicromegasPoolInput_v2.cc (1)

914-967: Reset fee_bco_predicted_matched when no prediction is available.

When the prediction is absent on a matched waveform, the field can retain the prior value and pollute QA output. Clearing it before the optional assignment avoids stale data.

♻️ Proposed fix
-        {
-          const auto predicted = bco_matching_information.get_predicted_fee_bco(gtm_bco);;
-          if( predicted )
-          {
-            m_waveform.fee_bco_predicted_matched = predicted.value();
-          }
-        }
+        m_waveform.fee_bco_predicted_matched = 0;
+        {
+          const auto predicted = bco_matching_information.get_predicted_fee_bco(gtm_bco);
+          if (predicted)
+          {
+            m_waveform.fee_bco_predicted_matched = predicted.value();
+          }
+        }
offline/packages/trackreco/DSTClusterPruning.cc (1)

173-211: Guard condition blocks pruneAllSeeds() in minimal setups.

The function requires m_track_seed_container unconditionally (line 173), but the pruneAllSeeds branch (lines 180–211) only needs TPC and Silicon seed containers. The non-pruneAllSeeds path (starting line 213) is where m_track_seed_container is actually used. Workflows without SvtxTrackSeedContainer that enable pruneAllSeeds will unnecessarily bail out.

🔧 Suggested fix
-  if (!(m_cluster_map && m_reduced_cluster_map && m_track_seed_container && m_silicon_track_seed_container && m_tpc_track_seed_container))
+  if (!(m_cluster_map && m_reduced_cluster_map && m_silicon_track_seed_container && m_tpc_track_seed_container))
   {
     if (Verbosity() > 1){
       std::cout << "Missing container" << std::endl;
     }
     return;
   }
+  if (!m_pruneAllSeeds && !m_track_seed_container)
+  {
+    if (Verbosity() > 1){
+      std::cout << "Missing track seed container" << std::endl;
+    }
+    return;
+  }
offline/packages/tpc/Tpc3DClusterizer.cc (1)

684-706: Uncomment rtree.remove() or make rtree parameter const-reference.

The rtree.remove(clusHit) call at line 694 is commented out, but rtree is passed by non-const reference. The similar implementation in LaserClusterizer::remove_hits (lines 223) actively calls rtree.remove(clusHit), suggesting this should not be commented out. Either:

  • Uncomment the removal to match the expected behavior
  • If rtree should remain unmodified, change the parameter to const bgi::rtree<...> &rtree to document intent

The rtree.empty() check (line 690) can be removed if rtree isn't being modified—it serves no algorithmic purpose and only logs a diagnostic message.

🤖 Fix all issues with AI agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc`:
- Around line 239-266: The AbsEta setters (SetAbsEtaHigh, SetAbsEtaLow,
SetAbsEtaHighLow) currently write into the signed-eta members
_theEtaHigh/_theEtaLow, causing signed-eta cuts to be unintentionally modified;
change the implementation to use dedicated abs-eta members (e.g. add
_theAbsEtaHigh and _theAbsEtaLow initialized in the constructor) and have
SetAbsEtaHigh/SetAbsEtaLow/SetAbsEtaHighLow assign those new members and set
_doAbsEtaHighCut/_doAbsEtaLowCut/_doBothAbsEtaCut, or alternatively explicitly
disable the signed-eta cuts inside those setters (clear
_doEtaHighCut/_doEtaLowCut) so the signed-eta thresholds are not used when
absolute-eta cuts are enabled; update any other occurrences (e.g. the similar
block at the other location noted) to use the new abs members or disabling
logic.
- Around line 22-58: The constructor HepMCParticleTrigger's initializer list
contains standalone commas that create empty initializers and break compilation;
edit the HepMCParticleTrigger(...) initializer list to remove the stray
comma-only lines so each member (threshold, goal_event_number, set_event_limit,
_theEtaHigh, _theEtaLow, _thePtHigh, _thePtLow, _thePHigh, _thePLow, _thePzHigh,
_thePzLow, _doEtaHighCut, _doEtaLowCut, _doBothEtaCut, _doAbsEtaHighCut,
_doAbsEtaLowCut, _doBothAbsEtaCut, _doPtHighCut, _doPtLowCut, _doBothPtCut,
_doPHighCut, _doPLowCut, _doBothPCut, _doPzHighCut, _doPzLowCut, _doBothPzCut)
is separated by a single comma with no empty lines; ensure no trailing commas or
blank comma lines remain in the initializer list for HepMCParticleTrigger.

In `@offline/framework/fun4all/InputFileHandler.cc`:
- Around line 183-187: The code builds a shell command by concatenating
m_RunBeforeOpeningScript, m_OpeningArgs and elements of stringvec into fullcmd
which allows shell injection; fix by avoiding passing a single shell
string—either construct an argument vector and invoke the helper directly with
an exec/spawn family call (use execvp/posix_spawn or equivalent) or, if
retaining shell execution, robustly escape/shell-quote each element
(m_OpeningArgs and each element of stringvec) before concatenation; update the
call site that uses fullcmd to accept an argv-style array or the sanitized,
safely-quoted command and reference the symbols m_RunBeforeOpeningScript,
m_OpeningArgs, stringvec and fullcmd when making changes.

In `@offline/framework/fun4allraw/Fun4AllStreamingInputManager.cc`:
- Line 1367: The calculation of ref_bco_minus_range in
Fun4AllStreamingInputManager.cc uses a fallback of m_mvtx_negative_bco when
m_RefBCO < m_mvtx_negative_bco, which is inconsistent with other places
(FillMvtx, FillInttPool) that default to 0; change the ternary so that when
m_RefBCO < m_mvtx_negative_bco the result defaults to 0 instead of
m_mvtx_negative_bco (i.e., update the computation of ref_bco_minus_range using
m_RefBCO, m_mvtx_negative_bco to return 0 on underflow) and ensure any logic
relying on ref_bco_minus_range continues to treat 0 as the minimum valid BCO.
- Around line 974-986: The computation of lower_limit in
Fun4AllStreamingInputManager::... can underflow when m_mvtx_is_triggered is
false (lower_limit = select_crossings - m_mvtx_bco_range - m_mvtx_negative_bco);
change this to perform a safe subtraction and clamp to zero (or use a
signed/extended intermediate like int64_t temp = (int64_t)select_crossings -
(int64_t)m_mvtx_bco_range - (int64_t)m_mvtx_negative_bco; lower_limit = temp < 0
? 0 : (uint64_t)temp) so that when m_RefBCO < m_mvtx_negative_bco you do not
wrap to a huge unsigned value; apply the same pattern/guarding used earlier
around line 839 and ensure the for-loop over m_MvtxRawHitMap continues to use
lower_limit and upper_limit as before.

In `@offline/framework/fun4allraw/SingleTriggeredInput.cc`:
- Around line 436-511: The skiptrace block leaks evt (and sometimes pkt) on
early continues and lacks handling when the 5-retry clock consistency loop
fails; update the block around GetEventIterator()/pkt retrieval so that whenever
you continue you first delete evt and/or pkt as appropriate (references:
GetEventIterator()->getNextEvent(), pkt from evt->getPacket(representative_pid),
FillPacketClock, m_bclkdiffarray_map, Gl1Input()/gl1), and after the
clockconsistency retry loop exhausts (clockconstcount>=5) add deterministic
failure handling—delete any allocated evt/pkt, log an error with Name(), and
propagate a failure (e.g. return -1 or call FilesDone/close) rather than falling
through so no dangling objects remain and misaligned data isn’t processed.

In `@offline/packages/CaloEmbedding/CombineTowerInfo.cc`:
- Around line 46-72: DetNode (the PHCompositeNode returned by iter.findFirst and
stored in DetNode) may be null before calling DetNode->addNode(...); update
CombineTowerInfo to check DetNode for null and handle it safely: if DetNode is
null either create and insert a new PHCompositeNode with the detector name into
the appropriate parent (so subsequent addNode calls are valid) or fail early
with a clear exception/log; ensure this null-check and handling occurs before
using DetNode to add the PHIODataNode for m_towersOut and reference the symbols
DetNode, m_outputNode, m_towersOut, and the PHIODataNode<PHObject> construction
in your change.

In `@offline/packages/CaloEmbedding/CopyIODataNodes.cc`:
- Around line 307-327: CopyTowerInfo dereferences TowerInfoContainer pointers
without null checks which can crash if findNode::getClass fails for
from_towerInfo_name or to_towerInfo_name; add null checks right after retrieving
from_towerInfo and to_towerInfo in CopyTowerInfo and bail out (log an error via
Verbosity()/std::cout or return) when either is null, and also guard the loop
that calls get_tower_at_channel/ch->copy_tower by verifying from_tow and the
result of to_towerInfo->get_tower_at_channel(ch) are non-null before calling
copy_tower; keep the existing identify() debug prints but only call them when
containers are valid.

In `@offline/packages/jetbackground/DetermineTowerBackground.cc`:
- Around line 76-108: The function DetermineTowerBackground::LoadCalibrations
currently calls exit(-1) when calibdir is empty; instead, remove the exit call
and return an error code so InitRun can handle it—specifically, when
calibdir.empty() is true (taking into account m_overwrite_average_calo_v2 and
m_overwrite_average_calo_v2_path), replace the exit(-1) with returning
Fun4AllReturnCodes::ABORTRUN and ensure callers (e.g., InitRun) will propagate
that failure.

In `@offline/packages/mvtx/MvtxHitPruner.cc`:
- Around line 158-167: Check for a nullptr return from m_hits->findHitSet inside
MvtxHitPruner::process_event before dereferencing hitset: if hitset is null,
emit a warning (or debug) message using the existing Verbosity() branch and skip
processing/continue the loop instead of accessing hitset->size() or calling
TrkrHitSet::ConstRange hitrangei = hitset->getHits(); this prevents
dereferencing a null pointer and keeps existing logging behavior consistent.

In `@offline/packages/tpc/Tpc3DClusterizer.cc`:
- Around line 659-681: The block that builds the fX array and calls
m_clusterNT->Fill(fX) must be guarded because m_clusterNT is only created when
m_output is true; restore a null-check or the original if (m_output) guard
around that block (or at minimum check m_clusterNT != nullptr before calling
Fill) to avoid a potential null pointer dereference; update the code around the
fX construction and the call to m_clusterNT->Fill so it only executes when
m_output is true and m_clusterNT is valid.
- Around line 554-559: The variables iphimin/iphimax are being assigned the
double angle phi (radians) causing type/semantic mismatch; either (A) treat them
as angle bounds: rename iphimin/iphimax to phimin/phimax and change their type
to double, update all uses (including phisize calculation) to compute angular
size from phimin/phimax, or (B) keep the iphi semantics and assign the integer
iphi (from coords[1]) instead of phi so iphimin/iphimax remain integer indices;
update any downstream code that computes phisize on the basis of index vs angle
accordingly (check uses of phi, iphi, coords[1], phisize to ensure consistency).

In `@offline/packages/trackreco/PHActsTrkFitter.cc`:
- Around line 391-409: The code currently leaves crossing_estimate set to
SHRT_MAX when m_pp_mode is true and m_enable_crossing_estimate is false, which
can propagate an invalid sentinel into drift corrections and fits; update the
logic in PHActsTrkFitter (the block referencing m_pp_mode,
m_enable_crossing_estimate, crossing, crossing_estimate and SHRT_MAX) to handle
the "no INTT crossing" case when estimate mode is disabled by either (1)
skipping the track (return/continue) or (2) setting crossing_estimate to a safe
default (e.g., 0) and not attempting fit; ensure use_estimate and nvary are only
set when m_enable_crossing_estimate is true and add a clear comment explaining
the chosen behavior.

In `@offline/QA/Tracking/StateClusterResidualsQA.cc`:
- Around line 181-198: The code calls
geometry->getGlobalPosition(state->get_cluskey(), cluster) before verifying
cluster is non-null, risking a crash; reorder and null-check so you call
cluster_map->findCluster(...) into cluster, then immediately if (!cluster)
continue (or skip) and only then call geometry->getGlobalPosition(..., cluster)
and use its result to compute cluster_x/cluster_y/cluster_z for filling
m_histograms_x/y/z; update the block around findCluster, getGlobalPosition, and
the if (cluster) check accordingly.

In `@offline/QA/Tracking/StateClusterResidualsQA.h`:
- Around line 56-101: These setters (setNMvtx, setNIntt, setNTpc, setPhiRange,
setEtaRange, setPtRange, setPositiveTracks, setNegativeTracks) assume
m_pending.back() exists and cause UB if called before an addHistogram; guard
each setter by ensuring m_pending is non-empty (e.g., if m_pending.empty()
push_back(default-constructed PendingEntry) or throw/assert with a clear
message) before accessing m_pending.back(), so callers can safely call any
setter before addHistogram; update all listed methods to perform the same
presence check and create a default pending entry when missing.
🟡 Minor comments (12)
offline/packages/mvtx/MvtxClusterPruner.cc-228-241 (1)

228-241: Guard against division by zero when computing fraction.

If no MVTX clusters are processed during the run (e.g., empty events or missing data), m_cluster_counter_total remains 0, causing undefined behavior on line 237.

Proposed fix
   std::cout << "MvtxClusterPruner::End -"
             << " m_cluster_counter_deleted: " << m_cluster_counter_deleted
-            << " fraction: " << double(m_cluster_counter_deleted) / m_cluster_counter_total
+            << " fraction: " << (m_cluster_counter_total > 0 ? double(m_cluster_counter_deleted) / m_cluster_counter_total : 0.0)
             << std::endl;
offline/framework/fun4all/InputFileHandler.cc-177-182 (1)

177-182: Permission check may be too restrictive.

The check only verifies owner_exec permission. If the process runs under a different user who has execute permission via group or other bits, the script will be incorrectly rejected.

Suggested fix: check any execute permission
-  if (!((std::filesystem::status(m_RunBeforeOpeningScript).permissions() & std::filesystem::perms::owner_exec) == std::filesystem::perms::owner_exec))
+  auto perms = std::filesystem::status(m_RunBeforeOpeningScript).permissions();
+  bool is_executable = (perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none ||
+                       (perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none ||
+                       (perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none;
+  if (!is_executable)
   {
     std::cout << PHWHERE << "RunBeforeOpeningScript script "
               << m_RunBeforeOpeningScript << " is not owner executable" << std::endl;
     return -1;
   }
offline/packages/CaloEmbedding/CopyIODataNodes.cc-322-322 (1)

322-322: Typo: "TowerInfoCOntainer" should be "TowerInfoContainer".

🔧 Fix typo
-    std::cout << "To TowerInfoCOntainer identify()" << std::endl;
+    std::cout << "To TowerInfoContainer identify()" << std::endl;
offline/packages/CaloEmbedding/CombineTowerInfo.cc-25-28 (1)

25-28: Missing validation for m_detector.

The m_detector member is used in CreateNodes to locate the detector node, but it's not validated in InitRun alongside the other node names.

🔧 Proposed fix
-  if (m_inputNodeA.empty() || m_inputNodeB.empty() || m_outputNode.empty())
+  if (m_inputNodeA.empty() || m_inputNodeB.empty() || m_outputNode.empty() || m_detector.empty())
   {
-    throw std::runtime_error("CombineTowerInfo: input/output node names not set");
+    throw std::runtime_error("CombineTowerInfo: input/output node names or detector not set");
   }
offline/packages/micromegas/MicromegasClusterizer.cc-159-166 (1)

159-166: Remove incomplete comment and use const reference in range-based for loop.

Line 166 contains //ma which appears to be an incomplete comment or debug artifact that should be removed before merging.

Additionally, consider using const std::string& in the range-based for loop to avoid unnecessary string copies.

Proposed fix
-  for( std::string geonodename: {"CYLINDERGEOM_MICROMEGAS_FULL", "CYLINDERGEOM_MICROMEGAS" } )
+  for( const std::string& geonodename: {"CYLINDERGEOM_MICROMEGAS_FULL", "CYLINDERGEOM_MICROMEGAS" } )
   {
     // try load node and test
     geonode = findNode::getClass<PHG4CylinderGeomContainer>(topNode, geonodename);
     if( geonode ) { break;}
   }
-
-  //ma
offline/packages/tpc/Tpc3DClusterizer.cc-150-158 (1)

150-158: Fix inconsistent fallback value for m_seed.

m_seed is initialized as int m_seed {-1}; in the header, but when the RANDOMSEED flag is absent, the code sets it to std::numeric_limits<int>::quiet_NaN(), which returns 0 for integer types. This creates an inconsistency in sentinel values.

Use -1 consistently instead of quiet_NaN(), or choose another explicit sentinel value that matches the member initialization.

offline/packages/trackreco/PHActsTrkFitter.cc-411-418 (1)

411-418: Clarify or enforce the “veto others” behavior in non‑pp mode.

The comment says “veto others,” but the code now forces crossing = 0 and continues. Either update the comment or re‑enable the veto (continue) so behavior matches intent.

Example: update comment to match behavior
-      // non pp mode, we want only crossing zero, veto others
+      // non-pp mode: force crossing to zero for all tracks
offline/packages/Skimmers/Trigger/TriggerDSTSkimmer.h-25-29 (1)

25-29: Guard against non‑positive max_events.

Passing 0 or negative values currently enables the cap and will abort all events. Consider treating <= 0 as “no cap” (or assert) to avoid accidental shutdowns.

🩹 Suggested fix
   void set_accept_max(int max_events) 
   {
-    use_max_accept = true;
-    max_accept = max_events;
-    return;
+    if (max_events <= 0)
+    {
+      use_max_accept = false;
+      max_accept = 0;
+      return;
+    }
+    use_max_accept = true;
+    max_accept = max_events;
   }
offline/packages/jetbackground/DetermineTowerBackground.h-41-45 (1)

41-45: Make SetOverwriteCaloV2 accept a const string reference.
Line 41 takes a non-const reference, which blocks temporaries and const strings. Other setters in this class (SetBackgroundOutputName, set_towerNodePrefix) correctly use const std::string&.

♻️ Proposed fix
-  void SetOverwriteCaloV2(std::string &url)
+  void SetOverwriteCaloV2(const std::string &url)
   {
     m_overwrite_average_calo_v2 = true;
     m_overwrite_average_calo_v2_path = url;
   }
offline/packages/jetbackground/DetermineTowerBackground.cc-948-971 (1)

948-971: Replace hardcoded centrality bin bounds with dynamic vector size check.

The _CENTRALITY_V2 vector is initialized with exactly 100 elements (indices 0–99) and populated for all centrality bins. The condition centrality_bin > 0 && centrality_bin < 95 incorrectly excludes valid bins 0 and 95–99, causing them to be treated as flow failures despite having populated v2 values. Use the vector size for bounds checking instead.

Fix
-      if (centrality_bin > 0 && centrality_bin < 95)
+      if (centrality_bin >= 0 &&
+          centrality_bin < static_cast<int>(_CENTRALITY_V2.size()))
       {
         _v2 = _CENTRALITY_V2[centrality_bin];
       }
offline/framework/fun4all/Fun4AllServer.cc-904-905 (1)

904-905: Add cout formatting restore to the NewSubsystems loop for consistency.

The Subsystems loop (line 904) restores cout formatting after calling BeginRunSubsystem, but the NewSubsystems loop (lines 906-910) does not. Since BeginRunSubsystem produces output that could modify formatting, this inconsistency can allow formatting changes to leak to subsequent code. Add the restore after the BeginRunSubsystem call in the NewSubsystems loop.

♻️ Proposed fix
   for (; !NewSubsystems.empty(); NewSubsystems.pop_front())
   {
     registerSubsystem((NewSubsystems.front()).first, (NewSubsystems.front()).second);
     BeginRunSubsystem(std::make_pair(NewSubsystems.front().first, topNode(NewSubsystems.front().second)));
+    std::cout.copyfmt(m_saved_cout_state);
   }
offline/packages/jetbackground/DetermineTowerBackground.cc-536-621 (1)

536-621: Add null check before dereferencing sEPDNS pointer.
Lines 605-607 assume EventplaneinfoMap::sEPDNS exists whenever the map is non-empty. The get() method returns nullptr if the key is absent, and a non-empty map does not guarantee this entry exists. Without a null check, dereferencing EPDNS will crash if sEPDNS was not inserted into the map.

♻️ Proposed fix
-      if (!(epmap->empty()))
-        {
-          auto *EPDNS = epmap->get(EventplaneinfoMap::sEPDNS);
-          _Psi2 = EPDNS->get_shifted_psi(2);
-        }
+      if (!(epmap->empty()))
+        {
+          auto *EPDNS = epmap->get(EventplaneinfoMap::sEPDNS);
+          if (EPDNS)
+          {
+            _Psi2 = EPDNS->get_shifted_psi(2);
+          }
+          else
+          {
+            _is_flow_failure = true;
+            _Psi2 = 0;
+          }
+        }
🧹 Nitpick comments (34)
offline/packages/mbd/MbdSig.h (1)

35-36: LGTM!

The new getter correctly exposes the private _nsamples member and follows the existing naming conventions in the class.

Optional improvement: Consider adding const qualifier for const-correctness:

-  int  GetNSamples() { return _nsamples; }
+  int  GetNSamples() const { return _nsamples; }

This applies to other getters in the class as well, but can be deferred to a broader const-correctness cleanup.

offline/packages/tpc/TpcClusterMover.cc (1)

161-191: LGTM! Const qualification is correct.

The method correctly doesn't modify any member state, so the const qualifier is appropriate.

Minor nit: There's an unnecessary nested brace block on lines 169-175. The outer braces around the if block serve no scoping purpose here.

🔧 Optional cleanup for redundant braces
   if (std::isnan(xplus))
   {
-    {
-      if (_verbosity > 1)
-      {
-        std::cout << " circle/circle intersection calculation failed, skip this cluster" << std::endl;
-        std::cout << " target_radius " << target_radius << " fitted R " << R << " fitted X0 " << X0 << " fitted Y0 " << Y0 << std::endl;
-      }
+    if (_verbosity > 1)
+    {
+      std::cout << " circle/circle intersection calculation failed, skip this cluster" << std::endl;
+      std::cout << " target_radius " << target_radius << " fitted R " << R << " fitted X0 " << X0 << " fitted Y0 " << Y0 << std::endl;
     }
     return Fun4AllReturnCodes::ABORTEVENT;  // skip to next cluster
   }
offline/packages/tpc/LaserEventIdentifier.cc (1)

141-141: Consider using integer arithmetic for BCO threshold comparison.

The comparison mixes unsigned integer subtraction with a floating-point expression (350.0/30*16 ≈ 186.67). If the intent is a timing window based on clock cycles, using explicit integer arithmetic would improve clarity.

💡 Suggested clarification
-    else if ((gl1pkt->getBCO() - prev_BCO) < 350.0/30*16)
+    // 350 ns timing window: 350/30 MHz * 16 clocks ≈ 186 BCO ticks
+    else if ((gl1pkt->getBCO() - prev_BCO) < 187)

Or keep the calculation explicit with a constexpr if the derivation is important for documentation.

offline/packages/tpc/TpcRawDataTree.cc (1)

180-206: Pre-existing: Duplicate m_fee == 16 condition causes dead code.

This is not related to your change, but m_fee == 16 appears in both conditions (line 185 and line 196). Since it's checked first in the R1 block, it will never match the R2 block—making the duplicate on line 196 unreachable. Consider removing it from one of the conditions or verifying which region FEE 16 should belong to.

🔧 Suggested fix
       else if (m_fee == 11 ||
                m_fee == 12 ||
                m_fee == 19 ||
                m_fee == 18 ||
                m_fee == 01 ||
                m_fee == 00 ||
-               m_fee == 16 ||
                m_fee == 15)
offline/framework/fun4allraw/SingleTriggeredInput.h (1)

115-115: Consider using m_ prefix for naming consistency.

Most private members in this class use the m_ prefix (e.g., m_EventNumber, m_Event, m_EventIterator), while eventcounter follows the pattern of existing exceptions like firstcall and firstclockcheck. For consistency with the dominant naming convention, consider renaming to m_EventCounter.

offline/packages/trackbase/AlignmentTransformation.h (1)

131-132: Consider passing use_module_tilt as a parameter to newMakeTransform instead of using member state.

This flag is set within a nested loop in createMap and read in newMakeTransform. Using member state for this transient communication is fragile—if newMakeTransform is ever called from another context, the state may be incorrect. Additionally, the comment "starts at false in all cases" is redundant given the initializer.

Suggested approach

Add a bool use_module_tilt parameter to newMakeTransform and pass the value directly from the call site, removing the member variable entirely.

offline/packages/trackbase/AlignmentTransformation.cc (1)

280-291: Consider replacing magic number test_layer < 4 with a named constant or clearer condition.

The value 4 is a magic number. Based on context, TPC module hitsetkeys have test_layer values of 0, 1, or 2 (for the three regions). A named constant or explicit comparison against the module layer range would improve readability.

Example
+          constexpr unsigned int kMaxModuleLayer = 3;
           use_module_tilt = false;
-          if (test_layer < 4 || use_module_tilt_always)
+          if (test_layer < kMaxModuleLayer || use_module_tilt_always)
offline/packages/mvtx/MvtxHitPruner.cc (2)

54-67: Consider marking begin() and end() as const member functions.

These accessor methods don't modify state and should be const for correctness and to allow use with const range_adaptor instances.

♻️ Suggested improvement
-    const typename T::first_type& begin() { return m_range.first; }
-    const typename T::second_type& end() { return m_range.second; }
+    const typename T::first_type& begin() const { return m_range.first; }
+    const typename T::second_type& end() const { return m_range.second; }

140-140: Consider using [[maybe_unused]] or just _ for the unused structured binding element.

The variable unused is intentionally unused. Using [[maybe_unused]] or the common convention of a single underscore makes the intent clearer and may suppress compiler warnings.

♻️ Suggested improvement
-    for (const auto& [unused, hitsetkey] : range_adaptor(bare_hitsetrange))
+    for ([[maybe_unused]] const auto& [unused, hitsetkey] : range_adaptor(bare_hitsetrange))

Or simply:

-    for (const auto& [unused, hitsetkey] : range_adaptor(bare_hitsetrange))
+    for (const auto& [_, hitsetkey] : range_adaptor(bare_hitsetrange))
offline/packages/PHGenFitPkg/PHGenFit/Fitter.cc (1)

53-58: Verify TGeoManager::Import ownership/overrides with pre-allocation.

_tgeo_manager is allocated before TGeoManager::Import(...); depending on ROOT semantics, Import may create/replace the current manager, which could leave _tgeo_manager unused or stale. Please confirm the ROOT version’s behavior and consider assigning _tgeo_manager to the Import result to avoid leaks or dangling references if replacement occurs.

offline/framework/fun4allraw/SingleMvtxPoolInput.cc (1)

463-467: Avoid the magic number 120 for m_NegativeBco

Line 466 hard-codes 120 without rationale. Consider a named constant (e.g., in MvtxRawDefs) or a brief comment tying it to the strobe-width window to prevent accidental regressions.

offline/packages/CaloEmbedding/CombineTowerInfo.cc (1)

85-95: Consider adding defensive null checks for tower pointers.

The get_tower_at_channel calls assume valid returns. While the size check in CreateNodes should ensure valid indices, defensive null checks would prevent crashes if tower data is inconsistent.

♻️ Optional defensive checks
   for (unsigned int ich = 0; ich < ntowers; ++ich)
   {
     TowerInfo* towerA = m_towersA->get_tower_at_channel(ich);
     TowerInfo* towerB = m_towersB->get_tower_at_channel(ich);
     TowerInfo* towerO = m_towersOut->get_tower_at_channel(ich);
+    if (!towerA || !towerB || !towerO)
+    {
+      continue;  // or log a warning
+    }

     towerO->copy_tower(towerA);
offline/packages/CaloEmbedding/CopyIODataNodes.h (2)

38-44: Minor: Unnecessary return statement in void setter.

The return; at line 43 is unnecessary in a void function. Other setters in this class (lines 31-37) don't use explicit returns.

♻️ Align with existing style
   void set_CopyTowerInfo(const std::string& set_from_towerInfo_name,const std::string& set_to_towerInfo_name)
   {
     from_towerInfo_name = set_from_towerInfo_name;
     to_towerInfo_name = set_to_towerInfo_name;
     m_CopyTowerInfoFlag = true; 
-    return;
   }

76-79: Naming inconsistency: missing m_ prefix on member variables.

The new from_towerInfo_name and to_towerInfo_name members don't follow the m_ prefix convention used by other members in this class.

♻️ Align naming with class convention
   bool m_CopyTowerInfoFlag = false;

-  std::string from_towerInfo_name = {};
-  std::string to_towerInfo_name = {};
+  std::string m_fromTowerInfoName;
+  std::string m_toTowerInfoName;
offline/packages/CaloEmbedding/CopyIODataNodes.cc (1)

369-369: Unconditional debug print should be gated by verbosity.

This print will appear for every run regardless of verbosity settings, unlike similar debug outputs elsewhere in this file which are guarded by Verbosity() > 0.

♻️ Gate debug output
 void CopyIODataNodes::CreateTowerInfo(PHCompositeNode *from_topNode, PHCompositeNode *to_topNode)
 {
-  std::cout << "copying tower info" << std::endl;
+  if (Verbosity() > 0)
+  {
+    std::cout << "copying tower info" << std::endl;
+  }
   TowerInfoContainer *from_towerInfo = findNode::getClass<TowerInfoContainer>(from_topNode, from_towerInfo_name);
offline/packages/tpc/Tpc3DClusterizer.cc (2)

284-343: Consider extracting magic numbers into named constants.

The iphi boundary values (191, 206, 383, 576, etc.) appear to be hardware-specific pad boundaries. While the brace additions are good, extracting these into named constants or a lookup table would improve maintainability and self-documentation.

♻️ Example refactor
// At class or namespace level
static constexpr std::array<int, 14> kExcludedPadBoundaries = {
    0, 191, 206, 383, 576, 767, 960, 1344, 1522, 1536, 1728, 1920, 2111, 2303
};
static constexpr std::array<int, 14> kBoundaryTolerances = {
    2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
};

// In the loop
if (layer >= 7 + 32)
{
  for (size_t i = 0; i < kExcludedPadBoundaries.size(); ++i)
  {
    if (abs(iphi - kExcludedPadBoundaries[i]) <= kBoundaryTolerances[i])
    {
      continue; // Note: would need restructuring to break outer loop
    }
  }
}

523-528: Use std::numeric_limits for min/max initialization.

The magic numbers (6666, -1, 66666666.6, -6666666666.6) are fragile. If hardware configurations change to support larger values, these could produce incorrect results.

♻️ Recommended fix
-  int iphimin = 6666;
-  int iphimax = -1;
-  int ilaymin = 6666;
-  int ilaymax = -1;
-  float itmin = 66666666.6;
-  float itmax = -6666666666.6;
+  int iphimin = std::numeric_limits<int>::max();
+  int iphimax = std::numeric_limits<int>::min();
+  int ilaymin = std::numeric_limits<int>::max();
+  int ilaymax = std::numeric_limits<int>::min();
+  float itmin = std::numeric_limits<float>::max();
+  float itmax = std::numeric_limits<float>::lowest();
offline/packages/tpc/LaserClusterizer.cc (3)

69-70: Consider making global arrays const.

The global arrays layerMins and layerMaxes appear to hold constant configuration data. Declaring them const (and potentially constexpr) would prevent accidental modification and clarify intent.

♻️ Proposed refactor
-int layerMins[3] = {7, 23, 39};
-int layerMaxes[3] = {22, 38, 54};
+constexpr int layerMins[3] = {7, 23, 39};
+constexpr int layerMaxes[3] = {22, 38, 54};

295-338: Consider using std::set for duplicate detection.

The current implementation performs linear searches through usedLayer, usedIPhi, and usedIT vectors for each hit, resulting in O(n²) complexity. Since <set> is already included (Line 51), using std::set would improve this to O(n log n).

♻️ Proposed refactor using std::set
-    std::vector<float> usedLayer;
-    std::vector<float> usedIPhi;
-    std::vector<float> usedIT;
+    std::set<float> usedLayer;
+    std::set<float> usedIPhi;
+    std::set<float> usedIT;

     // ... in the loop:
-      bool foundLayer = false;
-      for (float i : usedLayer)
-      {
-        if (coords[0] == i)
-        {
-          foundLayer = true;
-          break;
-        }
-      }
-
-      if (!foundLayer)
-      {
-        usedLayer.push_back(coords[0]);
-      }
+      usedLayer.insert(coords[0]);

-      bool foundIPhi = false;
-      for (float i : usedIPhi)
-      {
-        if (coords[1] == i)
-        {
-          foundIPhi = true;
-          break;
-        }
-      }
-
-      if (!foundIPhi)
-      {
-        usedIPhi.push_back(coords[1]);
-      }
+      usedIPhi.insert(coords[1]);

-      bool foundIT = false;
-      for (float i : usedIT)
-      {
-        if (coords[2] == i)
-        {
-          foundIT = true;
-          break;
-        }
-      }
-
-      if (!foundIT)
-      {
-        usedIT.push_back(coords[2]);
-      }
+      usedIT.insert(coords[2]);

Note: You'll also need to update the sorting and access patterns later (Lines 392-394, 409) to work with std::set iterators.


833-833: Consider extracting the event selection logic for clarity.

The conditional combines run number checks with different laser event flags, making it harder to understand at a glance. Extracting this logic to a named helper function would improve readability.

♻️ Proposed refactor

Add a helper method to the class:

bool LaserClusterizer::isValidLaserEvent() const
{
  const auto runNumber = eventHeader->get_RunNumber();
  if (runNumber > 66153)
  {
    return m_laserEventInfo->isGl1LaserEvent();
  }
  return m_laserEventInfo->isLaserEvent();
}

Then simplify the conditional:

-  if ((eventHeader->get_RunNumber() > 66153 && !m_laserEventInfo->isGl1LaserEvent()) || (eventHeader->get_RunNumber() <= 66153 && !m_laserEventInfo->isLaserEvent()))
+  if (!isValidLaserEvent())
   {
     return Fun4AllReturnCodes::EVENT_OK;
   }
offline/framework/fun4allraw/SingleTriggeredInput.cc (2)

355-359: Static local variable firstclockarray may not behave as intended.

The static bool firstclockarray is shared across all instances of SingleTriggeredInput and all iterations of the pid loop. It will only log for the very first pid encountered across the entire program lifetime, not once per instance or once per pool fill. If the intent is to log the first clock for each instance, this should be a member variable instead.

Consider using a member variable if per-instance logging is desired
-    static bool firstclockarray=true;
-    if(firstclockarray){
-      std::cout << "first clock call pid " << pid << " m_bclkarray_map[pid][0] : " << m_bclkarray_map[pid][0] << std::endl;
-      firstclockarray=false;
-    }
+    if(m_firstClockLogged.insert(pid).second) {
+      std::cout << "first clock call pid " << pid << " m_bclkarray_map[pid][0] : " << m_bclkarray_map[pid][0] << std::endl;
+    }

This requires adding std::unordered_set<int> m_firstClockLogged; as a member variable.


437-491: Consider extracting repeated event-fetch-with-file-handling pattern.

The pattern of fetching the next event with file close/reopen handling appears three times in FillEventVector() (lines 391-401, 440-448, 482-490). This duplication increases maintenance burden and risk of inconsistent behavior.

Consider extracting to a helper method
// Helper method to fetch next event, handling file transitions
Event* SingleTriggeredInput::FetchNextEvent()
{
  Event* evt = GetEventIterator()->getNextEvent();
  while (!evt)
  {
    fileclose();
    if (OpenNextFile() == InputFileHandlerReturnCodes::FAILURE)
    {
      FilesDone(1);
      return nullptr;
    }
    evt = GetEventIterator()->getNextEvent();
  }
  return evt;
}

Then replace the duplicated blocks with:

evt = FetchNextEvent();
if (!evt)
{
  return -1;
}

Additionally, consider making the retry limit (5) a named constant:

static constexpr int kMaxClockRecoveryRetries = 5;
offline/packages/tpc/TpcClusterizer.cc (1)

1205-1211: Guard the geometry down-casts to avoid UB.

static_cast assumes the container always returns PHG4TpcGeomv1. If a different version appears, this is undefined behavior. Consider dynamic_cast with a null check (or a checked helper) to fail fast.

♻️ Safer cast pattern (apply similarly to g2/g3)
-  auto *g1 = static_cast<PHG4TpcGeomv1*> (geom->GetFirstLayerCellGeom()); // cast because << not in the base class
+  auto *g1 = dynamic_cast<PHG4TpcGeomv1*>(geom->GetFirstLayerCellGeom());
+  if (!g1)
+  {
+    std::cout << PHWHERE << "Unexpected TPC geometry type for first layer" << std::endl;
+    return Fun4AllReturnCodes::ABORTRUN;
+  }
offline/framework/fun4allraw/Fun4AllStreamingInputManager.cc (1)

797-797: Minor: Trailing whitespace.

Line 797 has a trailing space after the closing brace.

Fix
-  } 
+  }
offline/packages/trackreco/PHActsTrkFitter.cc (1)

316-338: Guard silicon seed lookup before calling get().

m_siliconSeeds->get(siid) is called before checking siid == std::numeric_limits<unsigned int>::max(). If get() doesn’t tolerate invalid IDs, this can read past bounds. Consider guarding first or confirm get() safely returns nullptr for invalid IDs.

Suggested defensive guard
-    auto *siseed = m_siliconSeeds->get(siid);
-    if (siseed)
-    {
-      silicon_crossing = siseed->get_crossing();
-    }
+    TrackSeed* siseed = nullptr;
+    if (siid != std::numeric_limits<unsigned int>::max())
+    {
+      siseed = m_siliconSeeds->get(siid);
+      if (siseed)
+      {
+        silicon_crossing = siseed->get_crossing();
+      }
+    }
offline/QA/Tracking/MicromegasClusterQA.h (1)

54-58: Clarify the sample range as inclusive/exclusive.

The implementation uses a half‑open range (< m_sample_max), but the setter/member comments read like inclusive bounds. Clarifying avoids off‑by‑one misconfigurations.

✏️ Suggested comment tweak
-  /// set min sample for signal hits
+  /// set min sample for signal hits (inclusive)
   void set_sample_min(uint16_t value) { m_sample_min = value; }

-  /// set max sample for signal hits
+  /// set max sample for signal hits (exclusive)
   void set_sample_max(uint16_t value) { m_sample_max = value; }

-  /// min sample for signal
+  /// min sample for signal (inclusive)
   uint16_t m_sample_min = 0;

-  /// max sample for signal
+  /// max sample for signal (exclusive)
   uint16_t m_sample_max = 1024;

Also applies to: 108-113

offline/QA/Tracking/MicromegasClusterQA.cc (1)

76-78: Add a quick sanity check for sample range.

If m_sample_min >= m_sample_max, the new gating silently drops all clusters. A simple assert (or warning) in InitRun would make misconfigurations obvious.

✅ Minimal guard
   std::cout << "MicromegasClusterQA::InitRun - m_sample_min: " << m_sample_min << std::endl;
   std::cout << "MicromegasClusterQA::InitRun - m_sample_max: " << m_sample_max << std::endl;
+  assert(m_sample_min < m_sample_max);
offline/packages/micromegas/MicromegasCombinedDataDecoder.cc (1)

261-265: Gate duplicate-hit log behind Verbosity to avoid log spam.

This keeps logging consistent with the rest of the function and reduces overhead.

♻️ Proposed tweak
-    if (hit)
-    {
-      std::cout << "MicromegasCombinedDataDecoder::process_event - duplicated hit, hitsetkey: " << hitsetkey << " strip: " << strip << std::endl;
-      continue;
-    }
+    if (hit)
+    {
+      if (Verbosity())
+      {
+        std::cout << "MicromegasCombinedDataDecoder::process_event - duplicated hit, hitsetkey: " << hitsetkey << " strip: " << strip << std::endl;
+      }
+      continue;
+    }
generators/Herwig/HepMCTrigger/HepMCJetTrigger.cc (1)

130-133: Confirm hard |eta| cut is intended (or make it configurable).

This introduces a fixed acceptance cut before the pT threshold. Please confirm this matches the physics requirement, or consider exposing it as a parameter for flexibility.

offline/packages/Skimmers/Trigger/TriggerDSTSkimmer.cc (1)

54-54: Replace the magic GL1 packet ID with a named constant.

Using a named constant will make the intent clearer and reduce maintenance risk if the ID changes.

♻️ Proposed refactor
+namespace
+{
+constexpr int kGL1PacketNodeId = 14001;
+}  // namespace
+
-    Gl1Packet *_gl1PacketInfo = findNode::getClass<Gl1Packet>(topNode, 14001);
+    Gl1Packet *_gl1PacketInfo = findNode::getClass<Gl1Packet>(topNode, kGL1PacketNodeId);
generators/PHPythia8/PHPythia8.cc (1)

62-67: Prefer an RAII guard for cout formatting.

The manual save/restore pairs are easy to miss on future edits. A tiny guard makes restoration automatic on all paths (including early returns).

♻️ Suggested pattern (apply in ctor / Init / process_event)
+namespace
+{
+struct CoutFormatGuard
+{
+  explicit CoutFormatGuard(std::ostream& os) : _os(os), _old(nullptr) { _old.copyfmt(os); }
+  ~CoutFormatGuard() { _os.copyfmt(_old); }
+  std::ostream& _os;
+  std::ios _old;
+};
+}  // namespace
@@
-  std::ios old_state(nullptr);
-  old_state.copyfmt(std::cout);
-  m_Pythia8.reset(new Pythia8::Pythia(thePath));
-  std::cout.copyfmt(old_state);
+  CoutFormatGuard cout_guard(std::cout);
+  m_Pythia8.reset(new Pythia8::Pythia(thePath));
@@
-  std::ios old_state(nullptr);
-  old_state.copyfmt(std::cout); // save current state
+  CoutFormatGuard cout_guard(std::cout);
@@
-    std::cout.copyfmt(old_state); // restore state to saved state
-    return Fun4AllReturnCodes::ABORTRUN;
+    return Fun4AllReturnCodes::ABORTRUN;
@@
-  std::cout.copyfmt(old_state); // restore state to saved state

Also applies to: 126-133, 210-213, 288-289, 309-310

generators/Herwig/HepMCTrigger/HepMCParticleTrigger.h (1)

83-87: Align in‑class defaults with constructor initialization.

_thePtLow is initialized to -999.9 in the header but to 0 (then overridden) in the constructor. Consider aligning the in‑class default or removing it to avoid drift.

♻️ Possible adjustment
-  float _thePtLow{-999.9};
+  float _thePtLow{0};
offline/framework/fun4all/Fun4AllServer.cc (1)

131-134: Verify the cout baseline is intended to be the InitAll snapshot.
Line 131 captures std::cout once; subsequent restores reset formatting to that initial state, which can override caller formatting changes made after InitAll. If the intent is “restore to pre-call state,” consider capturing/restoring locally around each call (or refreshing the saved state).

generators/Herwig/HepMCTrigger/Makefile.am (1)

18-20: Consider linking the new lib in testexternals for a link check.
Line 19 adds libHepMCParticleTrigger.la, but testexternals still only links libHepMCJetTrigger.la, so the new lib isn’t link-checked. Consider including it there.

♻️ Proposed fix
-testexternals_LDADD   = libHepMCJetTrigger.la
+testexternals_LDADD   = libHepMCJetTrigger.la libHepMCParticleTrigger.la

Comment on lines +22 to +58
HepMCParticleTrigger::HepMCParticleTrigger(float trigger_thresh, int n_incom, bool up_lim, const std::string& name)
: SubsysReco(name)
, threshold(trigger_thresh)
, goal_event_number(n_incom)
, set_event_limit(up_lim)
, _theEtaHigh(1.1)
, _theEtaLow(-1.1)
, _thePtHigh(999.9)
, _thePtLow(0)
, _thePHigh(999.9)
, _thePLow(-999.9)
, _thePzHigh(999.9)
, _thePzLow(-999.9)
,

_doEtaHighCut(true)
, _doEtaLowCut(true)
, _doBothEtaCut(true)
,

_doAbsEtaHighCut(false)
, _doAbsEtaLowCut(false)
, _doBothAbsEtaCut(false)
,

_doPtHighCut(false)
, _doPtLowCut(false)
, _doBothPtCut(false)
,
_doPHighCut(false)
, _doPLowCut(false)
, _doBothPCut(false)
,

_doPzHighCut(false)
, _doPzLowCut(false)
, _doBothPzCut(false)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix initializer list syntax (standalone commas).

The empty comma-only lines create empty initializers and will not compile.

🐛 Proposed fix
   , _thePzHigh(999.9)
   , _thePzLow(-999.9)
-  ,
-
-    _doEtaHighCut(true)
+  , _doEtaHighCut(true)
   , _doEtaLowCut(true)
   , _doBothEtaCut(true)
-  ,
-
-  _doAbsEtaHighCut(false)
+  , _doAbsEtaHighCut(false)
   , _doAbsEtaLowCut(false)
   , _doBothAbsEtaCut(false)
-  ,
-
-  _doPtHighCut(false)
+  , _doPtHighCut(false)
   , _doPtLowCut(false)
   , _doBothPtCut(false)
-  ,
-    _doPHighCut(false)
+  , _doPHighCut(false)
   , _doPLowCut(false)
   , _doBothPCut(false)
-  ,
-
-  _doPzHighCut(false)
+  , _doPzHighCut(false)
   , _doPzLowCut(false)
   , _doBothPzCut(false)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HepMCParticleTrigger::HepMCParticleTrigger(float trigger_thresh, int n_incom, bool up_lim, const std::string& name)
: SubsysReco(name)
, threshold(trigger_thresh)
, goal_event_number(n_incom)
, set_event_limit(up_lim)
, _theEtaHigh(1.1)
, _theEtaLow(-1.1)
, _thePtHigh(999.9)
, _thePtLow(0)
, _thePHigh(999.9)
, _thePLow(-999.9)
, _thePzHigh(999.9)
, _thePzLow(-999.9)
,
_doEtaHighCut(true)
, _doEtaLowCut(true)
, _doBothEtaCut(true)
,
_doAbsEtaHighCut(false)
, _doAbsEtaLowCut(false)
, _doBothAbsEtaCut(false)
,
_doPtHighCut(false)
, _doPtLowCut(false)
, _doBothPtCut(false)
,
_doPHighCut(false)
, _doPLowCut(false)
, _doBothPCut(false)
,
_doPzHighCut(false)
, _doPzLowCut(false)
, _doBothPzCut(false)
HepMCParticleTrigger::HepMCParticleTrigger(float trigger_thresh, int n_incom, bool up_lim, const std::string& name)
: SubsysReco(name)
, threshold(trigger_thresh)
, goal_event_number(n_incom)
, set_event_limit(up_lim)
, _theEtaHigh(1.1)
, _theEtaLow(-1.1)
, _thePtHigh(999.9)
, _thePtLow(0)
, _thePHigh(999.9)
, _thePLow(-999.9)
, _thePzHigh(999.9)
, _thePzLow(-999.9)
, _doEtaHighCut(true)
, _doEtaLowCut(true)
, _doBothEtaCut(true)
, _doAbsEtaHighCut(false)
, _doAbsEtaLowCut(false)
, _doBothAbsEtaCut(false)
, _doPtHighCut(false)
, _doPtLowCut(false)
, _doBothPtCut(false)
, _doPHighCut(false)
, _doPLowCut(false)
, _doBothPCut(false)
, _doPzHighCut(false)
, _doPzLowCut(false)
, _doBothPzCut(false)
🤖 Prompt for AI Agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc` around lines 22 - 58,
The constructor HepMCParticleTrigger's initializer list contains standalone
commas that create empty initializers and break compilation; edit the
HepMCParticleTrigger(...) initializer list to remove the stray comma-only lines
so each member (threshold, goal_event_number, set_event_limit, _theEtaHigh,
_theEtaLow, _thePtHigh, _thePtLow, _thePHigh, _thePLow, _thePzHigh, _thePzLow,
_doEtaHighCut, _doEtaLowCut, _doBothEtaCut, _doAbsEtaHighCut, _doAbsEtaLowCut,
_doBothAbsEtaCut, _doPtHighCut, _doPtLowCut, _doBothPtCut, _doPHighCut,
_doPLowCut, _doBothPCut, _doPzHighCut, _doPzLowCut, _doBothPzCut) is separated
by a single comma with no empty lines; ensure no trailing commas or blank comma
lines remain in the initializer list for HepMCParticleTrigger.

Comment on lines +239 to +266
void HepMCParticleTrigger::SetAbsEtaHigh(double pt)
{
_theEtaHigh = pt;
_doAbsEtaHighCut = true;
if (_doAbsEtaLowCut)
{
_doBothAbsEtaCut = true;
}
return;
}
void HepMCParticleTrigger::SetAbsEtaLow(double pt)
{
_theEtaLow = pt;
_doAbsEtaLowCut = true;
if (_doAbsEtaHighCut)
{
_doBothAbsEtaCut = true;
}
return;
}
void HepMCParticleTrigger::SetAbsEtaHighLow(double ptHigh, double ptLow)
{
_theEtaHigh = ptHigh;
_doAbsEtaHighCut = true;
_theEtaLow = ptLow;
_doAbsEtaLowCut = true;
_doBothAbsEtaCut = true;
return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

AbsEta setters overwrite signed-eta thresholds (risk of asymmetric selection).

SetAbsEta* writes into _theEtaHigh/_theEtaLow, which are also used by the always-enabled signed-eta cuts. This means SetAbsEtaLow(>0) will also set the signed-eta lower bound > 0, effectively discarding all negative-eta particles. Unless that is intended, you should separate abs-eta thresholds (or disable signed-eta cuts when abs-eta cuts are enabled).

🔧 Suggested direction (separate abs-eta thresholds)
 void HepMCParticleTrigger::SetAbsEtaHigh(double pt)
 {
-  _theEtaHigh = pt;
+  _theAbsEtaHigh = pt;
   _doAbsEtaHighCut = true;
   if (_doAbsEtaLowCut)
   {
     _doBothAbsEtaCut = true;
   }
   return;
 }

 void HepMCParticleTrigger::SetAbsEtaLow(double pt)
 {
-  _theEtaLow = pt;
+  _theAbsEtaLow = pt;
   _doAbsEtaLowCut = true;
   if (_doAbsEtaHighCut)
   {
     _doBothAbsEtaCut = true;
   }
   return;
 }

 void HepMCParticleTrigger::SetAbsEtaHighLow(double ptHigh, double ptLow)
 {
-  _theEtaHigh = ptHigh;
+  _theAbsEtaHigh = ptHigh;
   _doAbsEtaHighCut = true;
-  _theEtaLow = ptLow;
+  _theAbsEtaLow = ptLow;
   _doAbsEtaLowCut = true;
   _doBothAbsEtaCut = true;
   return;
 }

-    if ((_doAbsEtaHighCut || _doBothAbsEtaCut) && std::abs(eta) > _theEtaHigh)
+    if ((_doAbsEtaHighCut || _doBothAbsEtaCut) && std::abs(eta) > _theAbsEtaHigh)
     {
       continue;
     }
-    if ((_doAbsEtaLowCut || _doBothAbsEtaCut) && std::abs(eta) < _theEtaLow)
+    if ((_doAbsEtaLowCut || _doBothAbsEtaCut) && std::abs(eta) < _theAbsEtaLow)
     {
       continue;
     }

Note: this requires adding _theAbsEtaHigh/_theAbsEtaLow members and constructor initialization in the header/initializer list.

Also applies to: 312-318

🤖 Prompt for AI Agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc` around lines 239 -
266, The AbsEta setters (SetAbsEtaHigh, SetAbsEtaLow, SetAbsEtaHighLow)
currently write into the signed-eta members _theEtaHigh/_theEtaLow, causing
signed-eta cuts to be unintentionally modified; change the implementation to use
dedicated abs-eta members (e.g. add _theAbsEtaHigh and _theAbsEtaLow initialized
in the constructor) and have SetAbsEtaHigh/SetAbsEtaLow/SetAbsEtaHighLow assign
those new members and set _doAbsEtaHighCut/_doAbsEtaLowCut/_doBothAbsEtaCut, or
alternatively explicitly disable the signed-eta cuts inside those setters (clear
_doEtaHighCut/_doEtaLowCut) so the signed-eta thresholds are not used when
absolute-eta cuts are enabled; update any other occurrences (e.g. the similar
block at the other location noted) to use the new abs members or disabling
logic.

Comment on lines +183 to +187
std::string fullcmd = m_RunBeforeOpeningScript + " " + m_OpeningArgs;
for (const auto& iter : stringvec)
{
fullcmd += " " + iter;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential shell command injection vulnerability.

The command is built by string concatenation without sanitizing m_OpeningArgs or elements of stringvec. If filenames or arguments contain shell metacharacters (e.g., ; rm -rf /, $(cmd), or backticks), they will be interpreted by the shell.

This is a security risk if:

  1. File paths come from untrusted sources (e.g., user-provided list files)
  2. m_OpeningArgs is set from external input

Consider quoting or escaping the arguments, or using an exec-style API that doesn't invoke a shell.

Minimal mitigation: shell-quote filenames
+#include <sstream>
+
+// Helper to shell-quote a string
+static std::string ShellQuote(const std::string& s) {
+  std::string result = "'";
+  for (char c : s) {
+    if (c == '\'') {
+      result += "'\\''";
+    } else {
+      result += c;
+    }
+  }
+  result += "'";
+  return result;
+}

-  std::string fullcmd = m_RunBeforeOpeningScript + " " + m_OpeningArgs;
+  std::string fullcmd = ShellQuote(m_RunBeforeOpeningScript);
+  if (!m_OpeningArgs.empty()) {
+    fullcmd += " " + m_OpeningArgs;  // User is responsible for quoting args
+  }
   for (const auto& iter : stringvec)
   {
-    fullcmd += " " + iter;
+    fullcmd += " " + ShellQuote(iter);
   }
🤖 Prompt for AI Agents
In `@offline/framework/fun4all/InputFileHandler.cc` around lines 183 - 187, The
code builds a shell command by concatenating m_RunBeforeOpeningScript,
m_OpeningArgs and elements of stringvec into fullcmd which allows shell
injection; fix by avoiding passing a single shell string—either construct an
argument vector and invoke the helper directly with an exec/spawn family call
(use execvp/posix_spawn or equivalent) or, if retaining shell execution,
robustly escape/shell-quote each element (m_OpeningArgs and each element of
stringvec) before concatenation; update the call site that uses fullcmd to
accept an argv-style array or the sanitized, safely-quoted command and reference
the symbols m_RunBeforeOpeningScript, m_OpeningArgs, stringvec and fullcmd when
making changes.

Comment on lines +974 to +986
uint64_t lower_limit = m_mvtx_is_triggered ? select_crossings : select_crossings - m_mvtx_bco_range - m_mvtx_negative_bco;
uint64_t upper_limit = m_mvtx_is_triggered ? select_crossings + m_mvtx_bco_range : select_crossings;

for (auto& [bco, hitinfo] : m_MvtxRawHitMap)
{
while (select_crossings <= m_MvtxRawHitMap.begin()->first && m_MvtxRawHitMap.begin()->first <= select_crossings + m_mvtx_bco_range) // triggered
if (bco < lower_limit)
{
if (Verbosity() > 2)
{
std::cout << "Adding 0x" << std::hex << m_MvtxRawHitMap.begin()->first
<< " ref: 0x" << select_crossings << std::dec << std::endl;
}
for (auto *mvtxFeeIdInfo : m_MvtxRawHitMap.begin()->second.MvtxFeeIdInfoVector)
{
if (Verbosity() > 1)
{
mvtxFeeIdInfo->identify();
}
mvtxEvtHeader->AddFeeIdInfo(mvtxFeeIdInfo);
delete mvtxFeeIdInfo;
}
m_MvtxRawHitMap.begin()->second.MvtxFeeIdInfoVector.clear();
mvtxEvtHeader->AddL1Trg(m_MvtxRawHitMap.begin()->second.MvtxL1TrgBco);
continue;
}
if (bco > upper_limit)
{
break;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential unsigned underflow in lower_limit calculation.

When m_mvtx_is_triggered is false, Line 974 computes:

lower_limit = select_crossings - m_mvtx_bco_range - m_mvtx_negative_bco

Since select_crossings = m_RefBCO + m_mvtx_bco_range (when not triggered), this simplifies to m_RefBCO - m_mvtx_negative_bco. If m_RefBCO < m_mvtx_negative_bco, this will underflow, causing lower_limit to wrap to a very large value and the loop to skip all entries.

Consider adding underflow protection consistent with line 839:

Proposed fix
-  uint64_t lower_limit = m_mvtx_is_triggered ? select_crossings : select_crossings - m_mvtx_bco_range - m_mvtx_negative_bco;
+  uint64_t lower_limit = m_mvtx_is_triggered ? select_crossings : (select_crossings > m_mvtx_bco_range + m_mvtx_negative_bco ? select_crossings - m_mvtx_bco_range - m_mvtx_negative_bco : 0);
🤖 Prompt for AI Agents
In `@offline/framework/fun4allraw/Fun4AllStreamingInputManager.cc` around lines
974 - 986, The computation of lower_limit in Fun4AllStreamingInputManager::...
can underflow when m_mvtx_is_triggered is false (lower_limit = select_crossings
- m_mvtx_bco_range - m_mvtx_negative_bco); change this to perform a safe
subtraction and clamp to zero (or use a signed/extended intermediate like
int64_t temp = (int64_t)select_crossings - (int64_t)m_mvtx_bco_range -
(int64_t)m_mvtx_negative_bco; lower_limit = temp < 0 ? 0 : (uint64_t)temp) so
that when m_RefBCO < m_mvtx_negative_bco you do not wrap to a huge unsigned
value; apply the same pattern/guarding used earlier around line 839 and ensure
the for-loop over m_MvtxRawHitMap continues to use lower_limit and upper_limit
as before.

int Fun4AllStreamingInputManager::FillMvtxPool()
{
uint64_t ref_bco_minus_range = m_RefBCO < m_mvtx_bco_range ? m_mvtx_bco_range : m_RefBCO - m_mvtx_bco_range;
uint64_t ref_bco_minus_range = m_RefBCO < m_mvtx_negative_bco ? m_mvtx_negative_bco : m_RefBCO - m_mvtx_negative_bco;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent underflow protection value.

Line 1367 defaults to m_mvtx_negative_bco when m_RefBCO < m_mvtx_negative_bco:

uint64_t ref_bco_minus_range = m_RefBCO < m_mvtx_negative_bco ? m_mvtx_negative_bco : m_RefBCO - m_mvtx_negative_bco;

This is inconsistent with:

  • Line 839 (FillMvtx): defaults to 0
  • Lines 1243-1247 (FillInttPool): defaults to 0

Defaulting to m_mvtx_negative_bco doesn't represent a valid minimum BCO value. This should likely default to 0 for consistency.

Proposed fix
-  uint64_t ref_bco_minus_range = m_RefBCO < m_mvtx_negative_bco ? m_mvtx_negative_bco : m_RefBCO - m_mvtx_negative_bco;
+  uint64_t ref_bco_minus_range = m_RefBCO < m_mvtx_negative_bco ? 0 : m_RefBCO - m_mvtx_negative_bco;
🤖 Prompt for AI Agents
In `@offline/framework/fun4allraw/Fun4AllStreamingInputManager.cc` at line 1367,
The calculation of ref_bco_minus_range in Fun4AllStreamingInputManager.cc uses a
fallback of m_mvtx_negative_bco when m_RefBCO < m_mvtx_negative_bco, which is
inconsistent with other places (FillMvtx, FillInttPool) that default to 0;
change the ternary so that when m_RefBCO < m_mvtx_negative_bco the result
defaults to 0 instead of m_mvtx_negative_bco (i.e., update the computation of
ref_bco_minus_range using m_RefBCO, m_mvtx_negative_bco to return 0 on
underflow) and ensure any logic relying on ref_bco_minus_range continues to
treat 0 as the minimum valid BCO.

Comment on lines +554 to +559
iphimin = std::min<double>(phi, iphimin);
iphimax = std::max<double>(phi, iphimax);
ilaymin = std::min(lay, ilaymin);
ilaymax = std::max(lay, ilaymax);
itmin = std::min<float>(tbin, itmin);
itmax = std::max<float>(tbin, itmax);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Variable naming inconsistency: iphimin/iphimax store phi (radians), not iphi (integer index).

Lines 554-555 assign phi (a double representing an angle in radians) to iphimin/iphimax, but the variable names suggest they should store integer phi indices. This causes:

  1. Precision loss when storing doubles in int variables
  2. Confusing semantics - the variables are used to compute phisize on line 631

Either rename the variables to phimin/phimax and change their type to double, or use the integer iphi value from coords[1] instead.

🐛 Proposed fix (if tracking phi angles)
-  int iphimin = 6666;
-  int iphimax = -1;
+  double phimin = std::numeric_limits<double>::max();
+  double phimax = std::numeric_limits<double>::lowest();
   ...
-  iphimin = std::min<double>(phi, iphimin);
-  iphimax = std::max<double>(phi, iphimax);
+  phimin = std::min(phi, phimin);
+  phimax = std::max(phi, phimax);
   ...
-  int phisize = iphimax - iphimin + 1;
+  double phisize = phimax - phimin;  // Note: units are radians, not bins
🐛 Alternative fix (if tracking iphi indices)
-  iphimin = std::min<double>(phi, iphimin);
-  iphimax = std::max<double>(phi, iphimax);
+  iphimin = std::min(static_cast<int>(coords[1]), iphimin);
+  iphimax = std::max(static_cast<int>(coords[1]), iphimax);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
iphimin = std::min<double>(phi, iphimin);
iphimax = std::max<double>(phi, iphimax);
ilaymin = std::min(lay, ilaymin);
ilaymax = std::max(lay, ilaymax);
itmin = std::min<float>(tbin, itmin);
itmax = std::max<float>(tbin, itmax);
iphimin = std::min(static_cast<int>(coords[1]), iphimin);
iphimax = std::max(static_cast<int>(coords[1]), iphimax);
ilaymin = std::min(lay, ilaymin);
ilaymax = std::max(lay, ilaymax);
itmin = std::min<float>(tbin, itmin);
itmax = std::max<float>(tbin, itmax);
🤖 Prompt for AI Agents
In `@offline/packages/tpc/Tpc3DClusterizer.cc` around lines 554 - 559, The
variables iphimin/iphimax are being assigned the double angle phi (radians)
causing type/semantic mismatch; either (A) treat them as angle bounds: rename
iphimin/iphimax to phimin/phimax and change their type to double, update all
uses (including phisize calculation) to compute angular size from phimin/phimax,
or (B) keep the iphi semantics and assign the integer iphi (from coords[1])
instead of phi so iphimin/iphimax remain integer indices; update any downstream
code that computes phisize on the basis of index vs angle accordingly (check
uses of phi, iphi, coords[1], phisize to ensure consistency).

Comment on lines 659 to +681
// if (m_output){
float fX[20] = {0};
int n = 0;
fX[n++] = m_event;
fX[n++] = m_seed;
fX[n++] = clusX;
fX[n++] = clusY;
fX[n++] = clusZ;
fX[n++] = clusR;
fX[n++] = clusPhi;
fX[n++] = clusiPhi;
fX[n++] = clusT;
fX[n++] = adcSum;
fX[n++] = maxAdc;
fX[n++] = (layerSum/adcSum);
fX[n++] = maxsector;
fX[n++] = maxside;
fX[n++] = nHits;
fX[n++] = phisize;
fX[n++] = tsize;
fX[n++] = lsize;
m_clusterNT->Fill(fX);
// }
float fX[20] = {0};
int n = 0;
fX[n++] = m_event;
fX[n++] = m_seed;
fX[n++] = clusX;
fX[n++] = clusY;
fX[n++] = clusZ;
fX[n++] = clusR;
fX[n++] = clusPhi;
fX[n++] = clusiPhi;
fX[n++] = clusT;
fX[n++] = adcSum;
fX[n++] = maxAdc;
fX[n++] = (layerSum / adcSum);
fX[n++] = maxsector;
fX[n++] = maxside;
fX[n++] = nHits;
fX[n++] = phisize;
fX[n++] = tsize;
fX[n++] = lsize;
m_clusterNT->Fill(fX);
// }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Potential null pointer dereference: m_clusterNT->Fill() called unconditionally.

m_clusterNT is only created when m_output is true (line 120), but m_clusterNT->Fill(fX) at line 680 is called unconditionally. If m_output is false, this will crash with a null pointer dereference.

The commented // if (m_output){ at line 659 and // } at line 681 suggest this guard was accidentally removed.

🐛 Proposed fix
-  // if (m_output){
   float fX[20] = {0};
   int n = 0;
   fX[n++] = m_event;
   ...
   fX[n++] = lsize;
-  m_clusterNT->Fill(fX);
-  // }
+  if (m_output)
+  {
+    m_clusterNT->Fill(fX);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// if (m_output){
float fX[20] = {0};
int n = 0;
fX[n++] = m_event;
fX[n++] = m_seed;
fX[n++] = clusX;
fX[n++] = clusY;
fX[n++] = clusZ;
fX[n++] = clusR;
fX[n++] = clusPhi;
fX[n++] = clusiPhi;
fX[n++] = clusT;
fX[n++] = adcSum;
fX[n++] = maxAdc;
fX[n++] = (layerSum/adcSum);
fX[n++] = maxsector;
fX[n++] = maxside;
fX[n++] = nHits;
fX[n++] = phisize;
fX[n++] = tsize;
fX[n++] = lsize;
m_clusterNT->Fill(fX);
// }
float fX[20] = {0};
int n = 0;
fX[n++] = m_event;
fX[n++] = m_seed;
fX[n++] = clusX;
fX[n++] = clusY;
fX[n++] = clusZ;
fX[n++] = clusR;
fX[n++] = clusPhi;
fX[n++] = clusiPhi;
fX[n++] = clusT;
fX[n++] = adcSum;
fX[n++] = maxAdc;
fX[n++] = (layerSum / adcSum);
fX[n++] = maxsector;
fX[n++] = maxside;
fX[n++] = nHits;
fX[n++] = phisize;
fX[n++] = tsize;
fX[n++] = lsize;
m_clusterNT->Fill(fX);
// }
if (m_output)
{
float fX[20] = {0};
int n = 0;
fX[n++] = m_event;
fX[n++] = m_seed;
fX[n++] = clusX;
fX[n++] = clusY;
fX[n++] = clusZ;
fX[n++] = clusR;
fX[n++] = clusPhi;
fX[n++] = clusiPhi;
fX[n++] = clusT;
fX[n++] = adcSum;
fX[n++] = maxAdc;
fX[n++] = (layerSum / adcSum);
fX[n++] = maxsector;
fX[n++] = maxside;
fX[n++] = nHits;
fX[n++] = phisize;
fX[n++] = tsize;
fX[n++] = lsize;
m_clusterNT->Fill(fX);
}
🤖 Prompt for AI Agents
In `@offline/packages/tpc/Tpc3DClusterizer.cc` around lines 659 - 681, The block
that builds the fX array and calls m_clusterNT->Fill(fX) must be guarded because
m_clusterNT is only created when m_output is true; restore a null-check or the
original if (m_output) guard around that block (or at minimum check m_clusterNT
!= nullptr before calling Fill) to avoid a potential null pointer dereference;
update the code around the fX construction and the call to m_clusterNT->Fill so
it only executes when m_output is true and m_clusterNT is valid.

Comment on lines +391 to +409
if (m_pp_mode)
{
if (m_enable_crossing_estimate && crossing == SHRT_MAX)
{
// this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
// If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
use_estimate = true;
nvary = max_bunch_search;
if (Verbosity() > 1)
{
std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
}
}
else
{
if (m_enable_crossing_estimate && crossing == SHRT_MAX)
{
// this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
// If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
use_estimate = true;
nvary = max_bunch_search;
if (Verbosity() > 1)
{
std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
}
}
else
{
// use INTT crossing
crossing_estimate = crossing;
}
// use INTT crossing
crossing_estimate = crossing;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid using SHRT_MAX as a real crossing when estimates are disabled.

In pp mode, if there’s a silicon seed but no INTT crossing and m_enable_crossing_estimate is false, the code proceeds with crossing_estimate = SHRT_MAX. That sentinel value is likely invalid for drift corrections and fit. Consider skipping the track or defaulting to a safe crossing (e.g., 0) when estimate mode is off.

One possible safeguard (skip when no crossing)
-    if (m_pp_mode)
-    {
-      if (m_enable_crossing_estimate && crossing == SHRT_MAX)
-      {
-        // this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
-        // If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
-        use_estimate = true;
-        nvary = max_bunch_search;
-        if (Verbosity() > 1)
-        {
-          std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
-        }
-      }
-      else
-      {
-        // use INTT crossing
-        crossing_estimate = crossing;
-      }
-    }
+    if (m_pp_mode)
+    {
+      if (m_enable_crossing_estimate && crossing == SHRT_MAX)
+      {
+        // this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
+        // If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
+        use_estimate = true;
+        nvary = max_bunch_search;
+        if (Verbosity() > 1)
+        {
+          std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
+        }
+      }
+      else if (crossing == SHRT_MAX)
+      {
+        if (Verbosity() > 1)
+        {
+          std::cout << " No INTT crossing and estimates disabled; skipping track" << std::endl;
+        }
+        continue;
+      }
+      else
+      {
+        // use INTT crossing
+        crossing_estimate = crossing;
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (m_pp_mode)
{
if (m_enable_crossing_estimate && crossing == SHRT_MAX)
{
// this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
// If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
use_estimate = true;
nvary = max_bunch_search;
if (Verbosity() > 1)
{
std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
}
}
else
{
if (m_enable_crossing_estimate && crossing == SHRT_MAX)
{
// this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
// If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
use_estimate = true;
nvary = max_bunch_search;
if (Verbosity() > 1)
{
std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
}
}
else
{
// use INTT crossing
crossing_estimate = crossing;
}
// use INTT crossing
crossing_estimate = crossing;
}
}
if (m_pp_mode)
{
if (m_enable_crossing_estimate && crossing == SHRT_MAX)
{
// this only happens if there is a silicon seed but no assigned INTT crossing, and only in pp_mode
// If there is no INTT crossing, start with the crossing_estimate value, vary up and down, fit, and choose the best chisq/ndf
use_estimate = true;
nvary = max_bunch_search;
if (Verbosity() > 1)
{
std::cout << " No INTT crossing: use crossing_estimate " << crossing_estimate << " with nvary " << nvary << std::endl;
}
}
else if (crossing == SHRT_MAX)
{
if (Verbosity() > 1)
{
std::cout << " No INTT crossing and estimates disabled; skipping track" << std::endl;
}
continue;
}
else
{
// use INTT crossing
crossing_estimate = crossing;
}
}
🤖 Prompt for AI Agents
In `@offline/packages/trackreco/PHActsTrkFitter.cc` around lines 391 - 409, The
code currently leaves crossing_estimate set to SHRT_MAX when m_pp_mode is true
and m_enable_crossing_estimate is false, which can propagate an invalid sentinel
into drift corrections and fits; update the logic in PHActsTrkFitter (the block
referencing m_pp_mode, m_enable_crossing_estimate, crossing, crossing_estimate
and SHRT_MAX) to handle the "no INTT crossing" case when estimate mode is
disabled by either (1) skipping the track (return/continue) or (2) setting
crossing_estimate to a safe default (e.g., 0) and not attempting fit; ensure
use_estimate and nvary are only set when m_enable_crossing_estimate is true and
add a clear comment explaining the chosen behavior.

Comment on lines +181 to +198
for (auto const& [path_length, state] : range_adaptor(track->begin_states(), track->end_states()))
{
if (path_length == 0) { continue; }

auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Null-check cluster before calling ActsGeometry::getGlobalPosition.

findCluster can return null; getGlobalPosition is invoked before the null check, which can crash.

🐛 Proposed fix
-          auto *cluster = cluster_map->findCluster(state->get_cluskey());
-          float state_x = state->get_x();
-          float state_y = state->get_y();
-          float state_z = state->get_z();
-          Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
-          float cluster_x = glob.x();
-          float cluster_y = glob.y();
-          float cluster_z = glob.z();
-          if (cluster)
-          {
-            m_histograms_x[h]->Fill(state_x - cluster_x);
-            m_histograms_y[h]->Fill(state_y - cluster_y);
-            m_histograms_z[h]->Fill(state_z - cluster_z);
-          }
+          auto *cluster = cluster_map->findCluster(state->get_cluskey());
+          if (!cluster) { continue; }
+
+          float state_x = state->get_x();
+          float state_y = state->get_y();
+          float state_z = state->get_z();
+          Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
+          float cluster_x = glob.x();
+          float cluster_y = glob.y();
+          float cluster_z = glob.z();
+          m_histograms_x[h]->Fill(state_x - cluster_x);
+          m_histograms_y[h]->Fill(state_y - cluster_y);
+          m_histograms_z[h]->Fill(state_z - cluster_z);
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.cc` around lines 181 - 198, The
code calls geometry->getGlobalPosition(state->get_cluskey(), cluster) before
verifying cluster is non-null, risking a crash; reorder and null-check so you
call cluster_map->findCluster(...) into cluster, then immediately if (!cluster)
continue (or skip) and only then call geometry->getGlobalPosition(..., cluster)
and use its result to compute cluster_x/cluster_y/cluster_z for filling
m_histograms_x/y/z; update the block around findCluster, getGlobalPosition, and
the if (cluster) check accordingly.

Comment on lines +56 to +101
StateClusterResidualsQA& setNMvtx(int min, int max)
{
m_pending.back().min_mvtx_clusters = min;
m_pending.back().max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
m_pending.back().min_intt_clusters = min;
m_pending.back().max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
m_pending.back().min_tpc_clusters = min;
m_pending.back().max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
m_pending.back().phi_min = min;
m_pending.back().phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
m_pending.back().eta_min = min;
m_pending.back().eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
m_pending.back().pt_min = min;
m_pending.back().pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
m_pending.back().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
m_pending.back().charge = -1;
return *this;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid UB when setters are called before addHistogram.

All setters assume m_pending is non-empty; calling a setter first dereferences an empty vector.

🐛 Proposed fix
 StateClusterResidualsQA& setNMvtx(int min, int max)
 {
-  m_pending.back().min_mvtx_clusters = min;
-  m_pending.back().max_mvtx_clusters = max;
+  auto& cfg = current_config();
+  cfg.min_mvtx_clusters = min;
+  cfg.max_mvtx_clusters = max;
   return *this;
 }
 StateClusterResidualsQA& setNIntt(int min, int max)
 {
-  m_pending.back().min_intt_clusters = min;
-  m_pending.back().max_intt_clusters = max;
+  auto& cfg = current_config();
+  cfg.min_intt_clusters = min;
+  cfg.max_intt_clusters = max;
   return *this;
 }
 StateClusterResidualsQA& setNTpc(int min, int max)
 {
-  m_pending.back().min_tpc_clusters = min;
-  m_pending.back().max_tpc_clusters = max;
+  auto& cfg = current_config();
+  cfg.min_tpc_clusters = min;
+  cfg.max_tpc_clusters = max;
   return *this;
 }
 StateClusterResidualsQA& setPhiRange(float min, float max)
 {
-  m_pending.back().phi_min = min;
-  m_pending.back().phi_max = max;
+  auto& cfg = current_config();
+  cfg.phi_min = min;
+  cfg.phi_max = max;
   return *this;
 }
 StateClusterResidualsQA& setEtaRange(float min, float max)
 {
-  m_pending.back().eta_min = min;
-  m_pending.back().eta_max = max;
+  auto& cfg = current_config();
+  cfg.eta_min = min;
+  cfg.eta_max = max;
   return *this;
 }
 StateClusterResidualsQA& setPtRange(float min, float max)
 {
-  m_pending.back().pt_min = min;
-  m_pending.back().pt_max = max;
+  auto& cfg = current_config();
+  cfg.pt_min = min;
+  cfg.pt_max = max;
   return *this;
 }
 StateClusterResidualsQA& setPositiveTracks()
 {
-  m_pending.back().charge = 1;
+  current_config().charge = 1;
   return *this;
 }
 StateClusterResidualsQA& setNegativeTracks()
 {
-  m_pending.back().charge = -1;
+  current_config().charge = -1;
   return *this;
 }
 
 private:
+  ResidualHistConfig& current_config()
+  {
+    if (m_pending.empty())
+    {
+      m_pending.emplace_back();
+    }
+    return m_pending.back();
+  }
   std::vector<ResidualHistConfig> m_pending;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
StateClusterResidualsQA& setNMvtx(int min, int max)
{
m_pending.back().min_mvtx_clusters = min;
m_pending.back().max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
m_pending.back().min_intt_clusters = min;
m_pending.back().max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
m_pending.back().min_tpc_clusters = min;
m_pending.back().max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
m_pending.back().phi_min = min;
m_pending.back().phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
m_pending.back().eta_min = min;
m_pending.back().eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
m_pending.back().pt_min = min;
m_pending.back().pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
m_pending.back().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
m_pending.back().charge = -1;
return *this;
}
StateClusterResidualsQA& setNMvtx(int min, int max)
{
auto& cfg = current_config();
cfg.min_mvtx_clusters = min;
cfg.max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
auto& cfg = current_config();
cfg.min_intt_clusters = min;
cfg.max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
auto& cfg = current_config();
cfg.min_tpc_clusters = min;
cfg.max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
auto& cfg = current_config();
cfg.phi_min = min;
cfg.phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
auto& cfg = current_config();
cfg.eta_min = min;
cfg.eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
auto& cfg = current_config();
cfg.pt_min = min;
cfg.pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
current_config().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
current_config().charge = -1;
return *this;
}
private:
ResidualHistConfig& current_config()
{
if (m_pending.empty())
{
m_pending.emplace_back();
}
return m_pending.back();
}
std::vector<ResidualHistConfig> m_pending;
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.h` around lines 56 - 101, These
setters (setNMvtx, setNIntt, setNTpc, setPhiRange, setEtaRange, setPtRange,
setPositiveTracks, setNegativeTracks) assume m_pending.back() exists and cause
UB if called before an addHistogram; guard each setter by ensuring m_pending is
non-empty (e.g., if m_pending.empty() push_back(default-constructed
PendingEntry) or throw/assert with a clear message) before accessing
m_pending.back(), so callers can safely call any setter before addHistogram;
update all listed methods to perform the same presence check and create a
default pending entry when missing.

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #14

coderabbitai bot added a commit that referenced this pull request Jan 19, 2026
Docstrings generation was requested by @blackcathj.

* #13 (comment)

The following files were modified:

* `generators/Herwig/HepMCTrigger/HepMCJetTrigger.cc`
* `generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc`
* `generators/Herwig/HepMCTrigger/HepMCParticleTrigger.h`
* `generators/PHPythia8/PHPythia8.cc`
* `generators/phhepmc/PHHepMCGenEventv1.cc`
* `offline/QA/Calorimeters/CaloValid.cc`
* `offline/QA/Tracking/MicromegasClusterQA.cc`
* `offline/QA/Tracking/MicromegasClusterQA.h`
* `offline/QA/Tracking/StateClusterResidualsQA.cc`
* `offline/QA/Tracking/StateClusterResidualsQA.h`
* `offline/framework/ffamodules/CDBInterface.cc`
* `offline/framework/ffamodules/FlagHandler.h`
* `offline/framework/ffamodules/HeadReco.cc`
* `offline/framework/ffamodules/HeadReco.h`
* `offline/framework/ffamodules/SyncReco.h`
* `offline/framework/ffamodules/Timing.cc`
* `offline/framework/ffamodules/Timing.h`
* `offline/framework/fun4all/Fun4AllServer.cc`
* `offline/framework/fun4all/Fun4AllServer.h`
* `offline/framework/fun4all/InputFileHandler.cc`
* `offline/framework/fun4all/InputFileHandler.h`
* `offline/framework/fun4allraw/Fun4AllStreamingInputManager.cc`
* `offline/framework/fun4allraw/SingleMicromegasPoolInput_v2.cc`
* `offline/framework/fun4allraw/SingleMicromegasPoolInput_v2.h`
* `offline/framework/fun4allraw/SingleMvtxPoolInput.cc`
* `offline/framework/fun4allraw/SingleTriggeredInput.cc`
* `offline/framework/fun4allraw/SingleTriggeredInput.h`
* `offline/packages/CaloEmbedding/CombineTowerInfo.cc`
* `offline/packages/CaloEmbedding/CombineTowerInfo.h`
* `offline/packages/CaloEmbedding/CopyIODataNodes.cc`
* `offline/packages/CaloEmbedding/CopyIODataNodes.h`
* `offline/packages/CaloReco/PhotonClusterBuilder.cc`
* `offline/packages/KFParticle_sPHENIX/KFParticle_sPHENIX.cc`
* `offline/packages/PHGenFitPkg/PHGenFit/Fitter.cc`
* `offline/packages/PHGenFitPkg/PHGenFit/Track.cc`
* `offline/packages/Skimmers/Trigger/TriggerDSTSkimmer.cc`
* `offline/packages/Skimmers/Trigger/TriggerDSTSkimmer.h`
* `offline/packages/jetbackground/DetermineTowerBackground.cc`
* `offline/packages/jetbackground/DetermineTowerBackground.h`
* `offline/packages/mbd/MbdCalib.cc`
* `offline/packages/mbd/MbdEvent.cc`
* `offline/packages/mbd/MbdReco.cc`
* `offline/packages/mbd/MbdSig.h`
* `offline/packages/micromegas/MicromegasClusterizer.cc`
* `offline/packages/micromegas/MicromegasCombinedDataDecoder.cc`
* `offline/packages/micromegas/MicromegasCombinedDataDecoder.h`
* `offline/packages/micromegas/MicromegasDefs.cc`
* `offline/packages/micromegas/MicromegasDefs.h`
* `offline/packages/mvtx/CylinderGeom_Mvtx.cc`
* `offline/packages/mvtx/CylinderGeom_Mvtx.h`
* `offline/packages/mvtx/MvtxClusterPruner.cc`
* `offline/packages/mvtx/MvtxClusterizer.cc`
* `offline/packages/mvtx/MvtxHitPruner.cc`
* `offline/packages/mvtx/SegmentationAlpide.cc`
* `offline/packages/tpc/LaserClusterizer.cc`
* `offline/packages/tpc/LaserEventIdentifier.cc`
* `offline/packages/tpc/Tpc3DClusterizer.cc`
* `offline/packages/tpc/TpcClusterMover.cc`
* `offline/packages/tpc/TpcClusterMover.h`
* `offline/packages/tpc/TpcClusterizer.cc`
* `offline/packages/tpc/TpcCombinedRawDataUnpacker.cc`
* `offline/packages/tpc/TpcCombinedRawDataUnpackerDebug.cc`
* `offline/packages/tpc/TpcDistortionCorrection.cc`
* `offline/packages/tpc/TpcLoadDistortionCorrection.cc`
* `offline/packages/tpc/TpcRawDataTree.cc`
* `offline/packages/tpc/TpcRawWriter.cc`
* `offline/packages/tpc/TpcSimpleClusterizer.cc`
* `offline/packages/tpc/TrainingHits.cc`
* `offline/packages/trackbase/AlignmentTransformation.cc`
* `offline/packages/trackbase/AlignmentTransformation.h`
* `offline/packages/trackreco/DSTClusterPruning.cc`
* `offline/packages/trackreco/DSTClusterPruning.h`
* `offline/packages/trackreco/PHActsTrkFitter.cc`
* `offline/packages/trackreco/PHActsTrkFitter.h`
* `offline/packages/trackreco/PHSiliconTpcTrackMatching.cc`
* `offline/packages/trackreco/PHSiliconTpcTrackMatching.h`
* `offline/packages/trackreco/PHSimpleVertexFinder.h`
* `offline/packages/trackreco/PHTpcDeltaZCorrection.h`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.