diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 01587643d..d990985d7 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -1326,7 +1326,20 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) { pass1.pass_number = 1; pass1.pass_count = 2; if (cancelled_) { config_.output_prefix = base_prefix; return pass1; } - if (gonio_snapshot) experiment_.Goniometer(*gonio_snapshot); // undo the pre-pass goniometer shift + // Undo the pre-pass goniometer shift (a sub-range or strided run moves start/increment inside + // the pass), but KEEP an axis direction the pass changed. The rotation-axis sign rescue runs + // inside the pass and adopts the opposite axis when the file's own indexes almost nothing; + // restoring the snapshot wholesale threw that away, handing pass 2 the sign that failed and + // then applying pass 1's fitted rotation scale to the axis it was not fitted on. It + // self-healed - pass 2's rescue fires again and a wrong sign indexes almost no frames - but + // only by repeating a whole first pass, and only while "a wrong sign always scores below + // half" holds, which is an invariant nothing states or tests. + if (gonio_snapshot) { + GoniometerAxis restored = *gonio_snapshot; + if (const auto after_pass1 = experiment_.GetGoniometer()) + restored.Axis(after_pass1->GetAxis()); + experiment_.Goniometer(restored); + } // A measured spot budget is the pass's own, not the run's: give the second pass the same list to // measure from, so the two passes cannot ratchet each other down. The canonical pass then reads // the budget off the refined geometry, where the spots that do lie on the lattice actually do. @@ -1475,8 +1488,15 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) { // only costs a pass on a crystal that was going to be wrong otherwise. experiment_.BeamX_pxl(header_geometry[0]).BeamY_pxl(header_geometry[1]) .DetectorDistance_mm(header_geometry[2]); - if (prepass_rotation_scale_ && gonio_snapshot) - experiment_.Goniometer(*gonio_snapshot); // and the header rotation angles with it + if (prepass_rotation_scale_ && gonio_snapshot) { + // The header rotation angles with it - but not the axis DIRECTION, which is not + // part of the geometry being reverted and may have been corrected by the sign + // rescue (see the restore before pass 2). + GoniometerAxis restored = *gonio_snapshot; + if (const auto current = experiment_.GetGoniometer()) + restored.Axis(current->GetAxis()); + experiment_.Goniometer(restored); + } config_.output_prefix = base_prefix; auto redo = RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false); redo.post_refine = pass1.post_refine;