/**
 **
 ** CuasPoseRT.java - RT pose-adjustment prototype: per-scene 3-angle pose
 ** fitting against the persistent virtual-center reference.
 **
 ** Copyright (C) 2026 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  CuasPoseRT.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.cuas.rt;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; // B1 task-oracle txy matching // By Claude on 07/16/2026
import java.util.concurrent.locks.LockSupport;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.gpu.GPUTileProcessor; // CORR_NTILE_SHIFT (peak-row mapping) // By Claude on 07/14/2026
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
import com.elphel.imagej.gpu.testdata.PoseCorrExport; // pose_corr kernel-test capture // By Claude on 07/13/2026
import com.elphel.imagej.tileprocessor.Correlation2d;
import com.elphel.imagej.tileprocessor.ErsCorrection;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.Interscene;
import com.elphel.imagej.tileprocessor.IntersceneLma;
import com.elphel.imagej.tileprocessor.IntersceneLmaFloat;
import com.elphel.imagej.tileprocessor.OpticalFlow;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.QuadCLTCPU;
import com.elphel.imagej.tileprocessor.TDCorrTile;
import com.elphel.imagej.tileprocessor.TwoQuadCLT;

import ij.ImagePlus;
import ij.ImageStack;
import ij.Prefs;
import ij.process.FloatProcessor;

/**
 * Phase A of the RT pose-adjustment package (design: ANDREY_CONTINUE 2026-07-03,
 * details in imagej-elphel-internal handoffs). A top-level scene iterator that
 * re-generates the per-scene 3-angle (az, tilt, roll) poses of a CUAS sequence
 * by fitting each scene directly against the persistent VIRTUAL-CENTER reference
 * (center CLT), in ascending time order, seeded by prediction — the RT processing
 * pattern. Uses the proven offline measurement engine
 * (Interscene.adjustPairsLMAInterscene(), which assumes the reference GPU data is
 * set once for all scenes); Phase B will swap that engine for the lean
 * TD-average x virtual-center correlation with the GPU argmax+eigen kernels,
 * keeping this iterator and its comparison output unchanged as the oracle.
 *
 * Differences from the offline setInitialOrientationsCuas():
 *  - ascending scene order (RT: "now" keeps arriving) instead of descending;
 *  - pose seed = previous fitted pose (zero-order prediction; the fit is anchored
 *    to the virtual center, so prediction only warm-starts the LMA and never
 *    accumulates into the geometry) instead of IMS integration;
 *  - single pass with the FINAL combo DSI (no refinement pass needed - the offline
 *    refinement only existed because disparity arrived after initial orientations);
 *  - tile selection by simple strength threshold (curt_pose_str) of the combo DSI
 *    (equivalent to old getReliableTiles: >1.0 gives 1074 vs 1055 tiles on the
 *    reference sequence); the optimized minimal selection is a future slow-loop task.
 *
 * Output: per-scene CSV (predicted/fitted/stored poses + deltas + rms) in the model
 * directory and a console summary of fitted-vs-stored statistics. Stored poses
 * (scenes_poses of the center ErsCorrection, restored from INTERFRAME corr-xml)
 * are the comparison truth - the re-adjusted offline set fitted from the same
 * DSI vintage.
 *
 * By Claude on 07/03/2026, from Andrey's design.
 */
public class CuasPoseRT {
	public static final double [] ZERO3 = {0.0, 0.0, 0.0};
	private static final double RT_FPS = 60.0;
	private static final ThreadLocal<RtPoseProfile> RT_POSE_PROFILE = new ThreadLocal<RtPoseProfile>();

	/** Wall-clock service/queue accounting for the all-scene preload harness. */
	private static final class RtPoseTiming {
		final boolean paced;
		final int totalInputs;
		final long periodNs = Math.round(1.0e9/RT_FPS);
		final long epochNs = System.nanoTime();
		final long [] conditionNs;
		final long [] postConditionNs;
		final long [] totalNs;
		int processed = 0;
		int inputFailures = 0;
		int oneFrameMisses = 0;
		int maxQueue = 0;
		double firstLatencyFrames = Double.NaN;
		double lastLatencyFrames = Double.NaN;
		double maxLatencyFrames = 0.0;
		long currentStartNs;
		long currentArrivalNs;
		long currentAfterConditionNs;
		long currentConditionNs;
		long lastFinishNs;

		RtPoseTiming(final boolean paced, final int totalInputs) {
			this.paced = paced;
			this.totalInputs = totalInputs;
			this.conditionNs = new long [totalInputs];
			this.postConditionNs = new long [totalInputs];
			this.totalNs = new long [totalInputs];
			System.out.println("CuasPoseRT RT HARNESS: "+(paced ? "paced 60 Hz" : "maximum throughput")+
					", all inputs consumed, no frame-skipping path");
		}

		void beforeScene() {
			currentArrivalNs = epochNs + processed*periodNs;
			if (paced) {
				for (;;) {
					final long remain = currentArrivalNs - System.nanoTime();
					if (remain <= 0) break;
					LockSupport.parkNanos(remain);
				}
			}
			currentStartNs = System.nanoTime();
			currentAfterConditionNs = currentStartNs;
			currentConditionNs = 0;
		}

		long conditionStart() { return System.nanoTime(); }

		void conditionDone(final long startNs) {
			currentAfterConditionNs = System.nanoTime();
			currentConditionNs = currentAfterConditionNs - startNs;
		}

		void finishScene(final boolean inputOk) {
			final long finish = System.nanoTime();
			if (!inputOk) inputFailures++;
			conditionNs[processed] = currentConditionNs;
			postConditionNs[processed] = finish - currentAfterConditionNs;
			totalNs[processed] = finish - currentStartNs;
			if (paced) {
				final double latency = (finish - currentArrivalNs)/(double) periodNs;
				if (processed == 0) firstLatencyFrames = latency;
				lastLatencyFrames = latency;
				if (latency > maxLatencyFrames) maxLatencyFrames = latency;
				if (latency > 1.0) oneFrameMisses++;
				final int arrived = Math.min(totalInputs,
						(int) ((Math.max(0L, finish - epochNs))/periodNs) + 1);
				final int queue = Math.max(0, arrived - (processed + 1));
				if (queue > maxQueue) maxQueue = queue;
			}
			processed++;
			lastFinishNs = finish;
		}

		void printSummary(final int poseFailures) {
			if (processed == 0) return;
			long sum = 0;
			for (int i = 0; i < processed; i++) sum += totalNs[i];
			final double computeRate = processed*1.0e9/sum;
			final double wallRate = processed*1.0e9/(lastFinishNs - epochNs);
			System.out.println("===== CUAS POSE RT HARD TIMING (file I/O and Row/Col application excluded) =====");
			printTiming("conditioning+H2D", conditionNs, processed);
			printTiming("post-conditioning pose/report", postConditionNs, processed);
			printTiming("whole scene service", totalNs, processed);
			System.out.println(String.format(
					"RT service: processed=%d/%d, compute_capacity=%.3f scenes/s, wall_rate=%.3f scenes/s, input_failures=%d, pose_coasts=%d, drops=0",
					processed, totalInputs, computeRate, wallRate, inputFailures, poseFailures));
			if (paced) {
				System.out.println(String.format(
						"RT 60Hz queue/latency: max_queue=%d, latency_first=%.3f frames, latency_last=%.3f frames, latency_max=%.3f frames, one-frame_deadline_misses=%d",
						maxQueue, firstLatencyFrames, lastLatencyFrames, maxLatencyFrames, oneFrameMisses));
			}
			final boolean pass = (processed == totalInputs) && (computeRate >= RT_FPS) &&
					(inputFailures == 0) && (poseFailures == 0);
			System.out.println("RT 60 FPS ACCEPTANCE: "+(pass ? "PASS" : "FAIL")+
					" (requires all scenes, compute_capacity>=60, zero input failures/coasts/drops; latency is pipeline depth only)");
		}
	}

	private static void printTiming(final String label, final long [] values, final int length) {
		final long [] sorted = Arrays.copyOf(values, length);
		Arrays.sort(sorted);
		final double p50 = sorted[(int) Math.floor(0.50*(length - 1))]*1.0e-6;
		final double p95 = sorted[(int) Math.floor(0.95*(length - 1))]*1.0e-6;
		final double p99 = sorted[(int) Math.floor(0.99*(length - 1))]*1.0e-6;
		final double max = sorted[length - 1]*1.0e-6;
		System.out.println(String.format("RT %-31s p50=%8.3f ms p95=%8.3f ms p99=%8.3f ms max=%8.3f ms",
				label+":", p50, p95, p99, max));
	}

	/**
	 * Low-perturbation hierarchical wall profiler for the preloaded pose harness.
	 * It stores primitive nanosecond samples and prints only once after the sequence;
	 * there are no per-boundary printlns in the measured loop. Native-call wall time
	 * deliberately includes any CUDA work/synchronization charged to that boundary.
	 * A later native/CUDA-event pass is only needed inside whichever boundary wins.
	 * By Codex on 07/15/2026 from Andrey's request to find the large hidden waste.
	 */
	private static final class RtPoseProfile {
		static final int POST_TOTAL = 0;
		static final int MOTION_BLUR = 1;
		static final int LEAN_FIT = 2;
		static final int REPORT_QC = 3;
		static final int MEASURE_TOTAL = 4;
		static final int GRID_TRANSFORM = 5;
		static final int TASK_BUILD_SORT = 6;
		static final int CONVERT_GEOMETRY = 7;
		static final int CONSOLIDATE = 8;
		static final int CORRELATE_TD = 9;
		static final int CORR_INDEX_D2H = 10;
		static final int NORMALIZE = 11;
		static final int DEBIAS_UPLOAD = 12;
		static final int PEAK_D2H = 13;
		static final int PEAK_UNPACK = 14;
		static final int DEBUG_FETCH = 15; // debug-save data motion (PD/composite), own stage so it can never hide in "other" (rung A) // By Claude on 07/16/2026
		static final int MEASURE_OTHER = 16;
		static final int LMA_PREPARE = 17;
		static final int PREP_SETUP = 18; // rung C0 sub-stages (design 2026-07-16) // By Claude on 07/17/2026
		static final int PREP_FX1 = 19;
		static final int PREP_WJTJ = 20;
		static final int PREP_FX2 = 21;
		static final int LMA_RUN = 22;
		static final int LMA_GPU_PRODUCTS = 23;
		static final int LMA_CPU_REMAINDER = 24;
		static final int POSE_UPDATE = 25;
		static final int FX_JNA = 26;      // cross-cutting: poseFxProvider JNA roundtrip, all lean fx sites // By Claude on 07/17/2026
		static final int FX_SETUPERS = 27; // cross-cutting: setupERS pair, lean fx sites
		static final int B3_CHAIN = 28;    // 3-B rung B3: single-entry batched measure chain (replaces stages 6-13 on batched cycles) // By Claude on 07/16/2026
		static final int DP_SCENE = 29;    // DP rung D4: ONE tp_proc_exec_pose_scene_dp entry per scene (replaces the whole measure<->solve loop on DP scenes) // By Claude on 07/17/2026
		static final int NUM_STAGES = 30;
		static final String [] LABELS = {
			"post-conditioning total",
			"  motion-blur setup",
			"  lean fit total",
			"  scene report/QC",
			"    leanMeasure total",
			"      grid transform (CPU)",
			"      task build+sort (CPU)",
			"      upload+geometry+2x convert",
			"      resident consolidation",
			"      TD correlation",
			"      corr-index D2H",
			"      FZ normalize",
			"      de-bias map upload/clear",
			"      peak kernel+D2H",
			"      peak unpack (Java)",
			"      debug-save fetch (PD+img)", // By Claude on 07/16/2026
			"      leanMeasure other",
			"    LMA prepare (CPU)",
			"      prep setup+samples weights",   // By Claude on 07/17/2026 (rung C0)
			"      prep fx pass 1 (getFxDerivs)",
			"      prep WJtJ+reg+normalize",
			"      prep fx pass 2 (getFxDerivs)",
			"    LMA run total",
			"      GPU normal products boundary",
			"      LMA CPU remainder",
			"    pose update/QC",
			"    fx JNA roundtrip (all sites)",    // By Claude on 07/17/2026 (rung C0)
			"    setupERS pair (all lean sites)",
			"      batched measure chain (B3)",   // By Claude on 07/16/2026 (rung B3; a leanMeasure child, printed last)
			"    scene DP entry (one JNA call)"   // By Claude on 07/17/2026 (rung D4; a lean-fit child on DP scenes)
		};

		final int scenesExpected;
		final long [][] values;
		final int [] counts = new int [NUM_STAGES];
		long currentLmaGpuNs;

		RtPoseProfile(final int scenesExpected, final int maxCycles) {
			this.scenesExpected = Math.max(1, scenesExpected);
			// Allow multiple inner-LMA provider calls without allocation or truncation.
			// The current validated shape uses one; 32 covers the legacy cap as well.
			final int capacity = this.scenesExpected * Math.max(1, maxCycles) * 32 + 8;
			this.values = new long [NUM_STAGES][capacity];
		}

		long start() { return System.nanoTime(); }

		long addElapsed(final int stage, final long startNs) {
			final long dt = System.nanoTime() - startNs;
			add(stage, dt);
			return dt;
		}

		void add(final int stage, final long ns) {
			if ((stage < 0) || (stage >= NUM_STAGES) || (ns < 0)) return;
			final int n = counts[stage];
			if (n < values[stage].length) {
				values[stage][n] = ns;
				counts[stage] = n + 1;
			}
		}

		void beginLmaRun() { currentLmaGpuNs = 0; }

		void addLmaGpuProducts(final long startNs) {
			final long dt = addElapsed(LMA_GPU_PRODUCTS, startNs);
			currentLmaGpuNs += dt;
		}

		void endLmaRun(final long startNs) {
			final long dt = addElapsed(LMA_RUN, startNs);
			add(LMA_CPU_REMAINDER, Math.max(0L, dt - currentLmaGpuNs));
		}

		private long sum(final int stage) {
			long s = 0;
			for (int i = 0; i < counts[stage]; i++) s += values[stage][i];
			return s;
		}

		private void printStage(final int stage, final long postSum) {
			final int n = counts[stage];
			if (n == 0) return;
			final long [] sorted = Arrays.copyOf(values[stage], n);
			Arrays.sort(sorted);
			final long total = sum(stage);
			final double mean = total*1.0e-6/n;
			final double p50 = sorted[(int) Math.floor(0.50*(n - 1))]*1.0e-6;
			final double p95 = sorted[(int) Math.floor(0.95*(n - 1))]*1.0e-6;
			final double max = sorted[n - 1]*1.0e-6;
			final double perScene = total*1.0e-6/scenesExpected;
			final double percent = (postSum > 0) ? 100.0*total/postSum : Double.NaN;
			System.out.println(String.format(
					"PROFILE %-35s calls=%4d mean=%8.3f p50=%8.3f p95=%8.3f max=%8.3f ms; %8.3f ms/scene %6.2f%% post",
					LABELS[stage]+":", n, mean, p50, p95, max, perScene, percent));
		}

		private void printDerived(final String label, final long total, final long postSum) {
			if (total <= 0) return;
			System.out.println(String.format(
					"PROFILE %-35s %8.3f ms/scene %6.2f%% post (derived remainder)",
					label+":", total*1.0e-6/scenesExpected,
					(postSum > 0) ? 100.0*total/postSum : Double.NaN));
		}

		void printSummary() {
			final long postSum = sum(POST_TOTAL);
			if (postSum == 0) return;
			System.out.println("===== CUAS POSE RT BOTTLENECK PROFILE (Java wall; one summary, no loop logging) =====");
			printStage(POST_TOTAL, postSum);
			printStage(MOTION_BLUR, postSum);
			printStage(LEAN_FIT, postSum);
			printStage(REPORT_QC, postSum);
			printDerived("  post other", postSum - sum(MOTION_BLUR) - sum(LEAN_FIT) - sum(REPORT_QC), postSum);
			printStage(MEASURE_TOTAL, postSum);
			printStage(B3_CHAIN, postSum); // rung B3: batched cycles (replaces stages 6-13) // By Claude on 07/16/2026
			for (int stage = GRID_TRANSFORM; stage <= MEASURE_OTHER; stage++) printStage(stage, postSum);
			printStage(LMA_PREPARE, postSum);
			for (int stage = PREP_SETUP; stage <= PREP_FX2; stage++) printStage(stage, postSum); // By Claude on 07/17/2026
			printDerived("      prep y+RMS/other",
					sum(LMA_PREPARE) - sum(PREP_SETUP) - sum(PREP_FX1) - sum(PREP_WJTJ) - sum(PREP_FX2), postSum);
			printStage(LMA_RUN, postSum);
			printStage(LMA_GPU_PRODUCTS, postSum);
			printStage(LMA_CPU_REMAINDER, postSum);
			printStage(POSE_UPDATE, postSum);
			printStage(DP_SCENE, postSum); // rung D4: DP scenes (replaces measure/prepare/run/update above) // By Claude on 07/17/2026
			printDerived("    lean-fit other",
					sum(LEAN_FIT) - sum(MEASURE_TOTAL) - sum(LMA_PREPARE) - sum(LMA_RUN) - sum(POSE_UPDATE) - sum(DP_SCENE), postSum);
			// Cross-cutting boundaries (rung C0): overlap LMA prepare/run above - not
			// part of any remainder arithmetic. By Claude on 07/17/2026.
			printStage(FX_JNA, postSum);
			printStage(FX_SETUPERS, postSum);
			System.out.println("PROFILE interpretation: Java wall >> later CUDA-event time means packing/JNA/synchronization waste; similar times mean device compute.");
		}
	}
	// Tile-selection CALIBRATION file (name says so - it must be PRESERVED with the model:
	// per-tile max-over-scenes residual, NaN = unusable; reliable_tiles are derived from it).
	// Renamed from -POSE-RT-MAXDXY per Andrey 07/05/2026; old name still read as fallback.
	public static final String TILE_CALIB_SUFFIX =     "-POSE-RT-TILE-CALIB";
	public static final String TILE_CALIB_SUFFIX_OLD = "-POSE-RT-MAXDXY";
	// Per-scene measurement characterization slices of the -POSE-RT-HYPER stack
	// (z = scenes, t = components, matching the renderSceneSequence make_hyper convention).
	// dx,dy,strength from coord_motion[1] (vector_XYS); the eigen four from coord_motion[2]
	// {eig_x,eig_y,l0,l1} (filled when imp.eig_use is on; NaN otherwise): sqrt_l0/sqrt_l1 =
	// correlation-peak ellipse half-axes (pix), elong = sqrt(l1/l0) (large = linear feature,
	// e.g. straight horizon: precise only perpendicular), eig0_ang = direction of the
	// precise axis (eigenvector of the smaller eigenvalue), radians [0,PI).
	public static final String [] HYPER_COMPONENTS =
		{"dx", "dy", "strength", "dxy", "sqrt_l0", "sqrt_l1", "elong", "eig0_ang"};

	// Cluster selection: a 3x3 window is a candidate if at least this many of its tiles
	// pass the outlier gate. Tiles are selected in whole clusters so every selected tile
	// has in-selection NEIGHBORS - the per-scene neighbor consolidation in the correlation
	// processing (min_str_neib/eig_str_neib) starves on scattered tiles (measured on
	// 1773135476_186641: tiles with ~2.7 selected neighbors were measured in >90% of
	// scenes, scattered tiles with ~0.7 neighbors almost never; corr 0.78).
	public static final int CLUSTER_MIN_ELIGIBLE = 6;

	/**
	 * Derive the tile selection from the per-tile max-residual calibration array
	 * (-POSE-RT-TILE-CALIB: max over scenes of measured dxy; NaN = unusable - either not
	 * selected/measured or NaN in at least one scene, always rejected). Stages (all
	 * scale-free, no absolute pixel constants):
	 *  1 - robust outlier gate: keep finite values <= median + k_nmad*NMAD of the
	 *      finite population (the "single high bump + long tail" histogram rule);
	 *  2 - compute budget: pick whole 3x3 CLUSTERS of gate-passing tiles (see
	 *      CLUSTER_MIN_ELIGIBLE) round-robin from three priority pools - LEFTMOST,
	 *      RIGHTMOST (per Andrey: edge tiles have the most influence on roll),
	 *      BEST-QUALITY - until num_tiles is reached. Clusters are disjoint. If
	 *      candidates run out, fill the remainder with best scattered tiles.
	 * By Claude on 07/04/2026, from Andrey's design.
	 */
	public static boolean [] deriveSelection(
			final float []  fmax,
			final int       tilesX,
			final double    k_nmad,      // <=0 - skip the gate
			final int       num_tiles,   // <=0 - no cap
			final int       border_tiles,// interior gate: exclude tiles within this many tiles of the array border, 0 - off // By Claude on 07/13/2026
			final int       debugLevel) {
		final boolean [] sel = new boolean [fmax.length];
		final int tilesY = fmax.length / tilesX;
		// Interior gate (3-A3, Andrey's ruling 07/13/2026, PURE BORDER RING - second
		// ruling same day): exclude tiles within border_tiles of the tile-array border.
		// The OOB-missing band is geometrically confined to the frame border (fitted
		// poses move the grid sub-pixel-to-few-px; margin-dropped centers = 1-2 tile
		// ring; partial 16-sensor OOB soft band 12 px = 1.5 tiles), so a ring of 4
		// covers hard+soft+slack and guarantees all-16-interior tiles at every scene
		// pose - the GPU pose chain can then use a FIXED per-sequence tile list with
		// the consolidation OOB pass bypassed. NO morphological shrink of the finite
		// mask: the calibration is NaN wherever a tile was not STRENGTH-selected (all
		// of the sky + scattered terrain holes, ~21% finite density) - a blind 2-tile
		// shrink annihilated the selection to ~2 tiles (07/13 run, killed).
		final boolean [] usable = new boolean [fmax.length];
		for (int i = 0; i < fmax.length; i++) {
			final int y = i / tilesX, x = i % tilesX;
			usable[i] = !Float.isNaN(fmax[i]) && !Float.isInfinite(fmax[i]) &&
					(y >= border_tiles) && (y < tilesY - border_tiles) &&
					(x >= border_tiles) && (x < tilesX - border_tiles);
		}
		float [] finite = new float [fmax.length];
		int num_finite = 0;
		for (int i = 0; i < fmax.length; i++) if (usable[i]) finite[num_finite++] = fmax[i];
		if (num_finite == 0) return sel;
		finite = Arrays.copyOf(finite, num_finite);
		Arrays.sort(finite);
		final double median = finite[num_finite/2];
		final float [] adev = new float [num_finite];
		for (int i = 0; i < num_finite; i++) adev[i] = (float) Math.abs(finite[i] - median);
		Arrays.sort(adev);
		final double nmad = 1.4826 * adev[num_finite/2];
		final double gate = (k_nmad > 0) ? (median + k_nmad * nmad) : Double.POSITIVE_INFINITY;
		final boolean [] eligible = new boolean [fmax.length];
		int num_gated = 0;
		for (int i = 0; i < fmax.length; i++) {
			eligible[i] = usable[i] && (fmax[i] <= gate); // usable folds in the interior border ring // By Claude on 07/13/2026
			if (eligible[i]) num_gated++;
		}
		if (debugLevel > -4) {
			System.out.println(String.format(
					"CuasPoseRT.deriveSelection(): usable=%d (border ring %d), median=%.3f, NMAD=%.3f, gate=%.3f (k=%.2f) -> %d gated tiles",
					num_finite, border_tiles, median, nmad, gate, k_nmad, num_gated));
		}
		if ((num_tiles <= 0) || (num_gated <= num_tiles)) {
			System.arraycopy(eligible, 0, sel, 0, sel.length);
			return sel;
		}
		// ---- Stage 2: disjoint 3x3 clusters, round-robin left / right / best-quality ----
		final ArrayList<int []> cand = new ArrayList<int []>(); // {center_index, n_eligible}
		final float [] cquality = new float [fmax.length];      // median fmax of eligible members
		for (int cy = 1; cy < tilesY - 1; cy++) {
			for (int cx = 1; cx < tilesX - 1; cx++) {
				final float [] q = new float [9];
				int n = 0;
				for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) {
					final int i = (cy + dy) * tilesX + (cx + dx);
					if (eligible[i]) q[n++] = fmax[i];
				}
				if (n >= CLUSTER_MIN_ELIGIBLE) {
					final int ci = cy * tilesX + cx;
					Arrays.sort(q, 0, n);
					cquality[ci] = q[n/2];
					cand.add(new int [] {ci, n});
				}
			}
		}
		// three priority orders over the same candidates
		final ArrayList<int []> by_left =    new ArrayList<int []>(cand);
		final ArrayList<int []> by_right =   new ArrayList<int []>(cand);
		final ArrayList<int []> by_quality = new ArrayList<int []>(cand);
		by_left.sort   ((a,b) -> Integer.compare(a[0] % tilesX, b[0] % tilesX));
		by_right.sort  ((a,b) -> Integer.compare(b[0] % tilesX, a[0] % tilesX));
		by_quality.sort((a,b) -> Float.compare(cquality[a[0]], cquality[b[0]]));
		final ArrayList<ArrayList<int []>> pools = new ArrayList<ArrayList<int []>>();
		pools.add(by_left); pools.add(by_right); pools.add(by_quality);
		final int [] pool_pos = new int [pools.size()];
		final int [] pool_used = new int [pools.size()];
		int num_sel = 0, num_clusters = 0;
		while (num_sel < num_tiles) {
			boolean any = false;
			for (int p = 0; (p < pools.size()) && (num_sel < num_tiles); p++) {
				final ArrayList<int []> pool = pools.get(p);
				while (pool_pos[p] < pool.size()) {
					final int ci = pool.get(pool_pos[p]++)[0];
					final int cy = ci / tilesX, cx = ci % tilesX;
					boolean overlap = false;
					for (int dy = -1; (dy <= 1) && !overlap; dy++) for (int dx = -1; dx <= 1; dx++) {
						if (sel[(cy + dy) * tilesX + (cx + dx)]) { overlap = true; break; }
					}
					if (overlap) continue;
					for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) {
						final int i = (cy + dy) * tilesX + (cx + dx);
						if (eligible[i] && !sel[i] && (num_sel < num_tiles)) {
							sel[i] = true;
							num_sel++;
						}
					}
					num_clusters++;
					pool_used[p]++;
					any = true;
					break;
				}
			}
			if (!any) break; // all pools exhausted
		}
		if (num_sel < num_tiles) { // fill the remainder with best scattered eligible tiles
			final int [] idx = new int [num_gated];
			int n = 0;
			for (int i = 0; i < fmax.length; i++) if (eligible[i] && !sel[i]) idx[n++] = i;
			final Integer [] order = new Integer [n];
			for (int j = 0; j < n; j++) order[j] = j;
			final int [] fidx = Arrays.copyOf(idx, n);
			Arrays.sort(order, (a,b) -> Float.compare(fmax[fidx[a]], fmax[fidx[b]]));
			for (int j = 0; (j < n) && (num_sel < num_tiles); j++) {
				sel[fidx[order[j]]] = true;
				num_sel++;
			}
		}
		if (debugLevel > -4) {
			float worst = 0;
			for (int i = 0; i < fmax.length; i++) if (sel[i] && (fmax[i] > worst)) worst = fmax[i];
			System.out.println(String.format(
					"CuasPoseRT.deriveSelection(): %d clusters (left/right/quality = %d/%d/%d) -> %d tiles, worst kept max=%.3f pix",
					num_clusters, pool_used[0], pool_used[1], pool_used[2], num_sel, worst));
		}
		return sel;
	}

	/**
	 * Phase B lean measurement: ONE scene-vs-virtual-center correlation per LMA cycle.
	 * Average the 16 sensors in TD BEFORE correlation ("multiply averages, not average
	 * products"), then a single conj-multiply against the persistent center TD:
	 *   interCorrTD(sensor_mask=0)  - tasks(pose) + offsets + convert_direct ONLY
	 *   CuasTD.consolidateToSlot0  - the consolidation bridge: GPU v1 chain on the JNA
	 *       backend (on-device, OOB soft/hard filtered), CPU bridge (getCltData ->
	 *       consolidateSensorsTD -> setCltData(0)) on JCuda // By Claude on 07/12/2026
	 *   setSensorMaskInter(1) + execCorr2D_inter_TD  - the single conj-multiply
	 *   TDCorrTile.getFromGpu -> TDCorrTile.convertTDtoPD  - FZ-normalize + PD (existing)
	 *   Correlation2d.getMaxXYCmEig  - peak + eigen (the GPU argmax kernel's oracle)
	 * All GPU stages are existing kernels (the correlate/normalize/PD trio is the
	 * JNA-validated CuasMotion.correlatePairs path). Correlation stages are geometry-
	 * blind: all projection/distortion is baked into the tasks (average-camera grid), so
	 * the measured (dx,dy) is displacement on the same grid the pose Jacobian uses.
	 * Differences from the oracle engine (adjustPairsLMAInterscene) in this v1: no FPN
	 * peak masking (input is FPN-subtracted by conditioning), no moving-object filter,
	 * min_confidence not applied (tiles are pre-calibrated).
	 * By Claude on 07/05/2026, from Andrey's design.
	 * @param corr_pd_out null or length-1 holder - filled with the pixel-domain
	 *        correlation tiles ([tile][15*15], dense grid index) for debug rendering
	 * @param img_out null or length-1 holder - filled with the imclt render of the
	 *        consolidated composite scene ([width*height], NaN outside task tiles);
	 *        requesting it adds an erased re-convert + an imclt pass (debug only)
	 * @param mb_vectors null (no motion-blur compensation) or [2][ntiles] blur vectors -
	 *        the certified-chain MB: two task sets, double convert (positive SET +
	 *        negative shifted SUBTRACT), erase-first so undefined tiles stay NaN
	 * @param mb_tau bolometer time constant, s (imp.mb_tau)
	 * @param mb_max_gain max MB scale before offset stretching (imp.mb_max_gain_inter)
	 * @return [3][num_pix][] {centers pXpYD, vector_XYS {dx,dy,strength}, eigen
	 *         {e0x,e0y,l0,l1}} - the coord_motion contract of interCorrPair - or null
	 */
	public static double [][][] leanMeasure(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double []     center_disparity,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double []     scene_xyz,
			final double []     scene_atr,
			final int           margin,
			final double [][]   mb_vectors,  // By Claude on 07/05/2026
			final double        mb_tau,      // By Claude on 07/05/2026
			final double        mb_max_gain, // By Claude on 07/05/2026
			final double [][][] corr_pd_out, // By Claude on 07/04/2026
			final float [][]    img_out,     // By Claude on 07/04/2026
			final int           debugLevel) {
		final RtPoseProfile rtProfile = RT_POSE_PROFILE.get();
		final long measureStart = (rtProfile != null) ? rtProfile.start() : 0L;
		long measureAccounted = 0L;
		final GpuQuad gpuQuad = center_CLT.getGPUQuad();
		final int num_pix = pXpYD_center.length;
		final double disparity_corr = clt_parameters.imp.disparity_corr + center_CLT.getDispInfinityRef(); // hoisted for B1 // By Claude on 07/16/2026
		long profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
		// 3-B rung B1 (design 2026-07-16): resident GPU task build - pose_task_update
		// projects the per-sequence template on-device from the current pose and
		// rewrites both native task slots in place; the full-grid transform and the
		// CPU task build below become the fallback (JCuda backend, non-uniform MB,
		// degenerate uniform fallback). By Claude on 07/16/2026.
		TpTask [][] b1_tasks = null;
		int [] b3_stats = null; // rung B3: non-null = the batched single-entry chain ran inside gpuTaskBuild // By Claude on 07/16/2026
		if ((mb_vectors != null) && clt_parameters.curt.pose_mb_uniform &&
				gpuQuad.supportsResidentTaskSlots()) {
			// rung B3 eligibility: production batching starts after the one-shot
			// batched-vs-granular oracle reported (pose_lma_debug>=1 arms it on the
			// first cycle); debug-fetch cycles and armed pose_corr captures stay
			// granular (they read mid-chain state). By Claude on 07/16/2026.
			final boolean b3_try = gpuQuad.supportsPoseMeasureCycle() &&
					!PoseCorrExport.isArmed() && !b3_oracle_failed &&
					(corr_pd_out == null) && (img_out == null) &&
					((clt_parameters.curt.pose_lma_debug < 1) || b3_oracle_reported);
			if (b3_try) b3_stats = new int [3];
			b1_tasks = gpuTaskBuild(clt_parameters, image_dtt, center_CLT, scene,
					center_disparity, pXpYD_center, selection, scene_xyz, scene_atr, margin,
					mb_vectors, mb_tau, mb_max_gain, disparity_corr, gpuQuad, b3_stats, debugLevel);
			if (b1_tasks == null) b3_stats = null; // degenerate/non-uniform -> legacy CPU build
		}
		final boolean b1_built = (b1_tasks != null);
		final boolean b3_ran = (b3_stats != null); // By Claude on 07/16/2026
		// rung B3 profile: the batched chain ran INSIDE gpuTaskBuild - attribute it
		// from the pre-build timestamp (the 22:09 gate run took a fresh start below
		// and recorded ~0, leaking ~2 ms/cycle into "leanMeasure other").
		// By Claude on 07/16/2026.
		if (b3_ran && (rtProfile != null)) {
			measureAccounted += rtProfile.addElapsed(RtPoseProfile.B3_CHAIN, profileStart);
		}
		// 1. scene grid at the current pose estimate (average-camera coordinates) -
		//    LEGACY CPU front end only (B1 projects on-device) // By Claude on 07/16/2026
		final double [][] scene_pXpYD = b1_built ? null : OpticalFlow.transformToScenePxPyD(
				null, center_disparity, scene_xyz, scene_atr, scene, center_CLT);
		if (!b1_built && (rtProfile != null)) measureAccounted += rtProfile.addElapsed(RtPoseProfile.GRID_TRANSFORM, profileStart);
		// B0 grid-transform attribution (one-shot): full-grid vs selection-only,
		// multi-thread vs single-thread. churn ~= multi - single/threads;
		// full-grid waste ~= full - selected-only. By Claude on 07/16/2026.
		if (!b1_built && (clt_parameters.curt.pose_lma_debug >= 1) && !b0_grid_reported) { // legacy-path diagnostic // By Claude on 07/16/2026
			b0_grid_reported = true;
			int n_def = 0;
			for (double [] p : scene_pXpYD) if (p != null) n_def++;
			int n_sel = 0;
			for (boolean s : selection) if (s) n_sel++;
			final double [] sel_disparity = center_disparity.clone();
			for (int i = 0; i < sel_disparity.length; i++) if (!selection[i]) sel_disparity[i] = Double.NaN;
			final long b0_t0 = System.nanoTime();
			OpticalFlow.transformToScenePxPyD(null, center_disparity, scene_xyz, scene_atr, scene, center_CLT); // warm full multi
			final long b0_t1 = System.nanoTime();
			OpticalFlow.transformToScenePxPyD(null, center_disparity, scene_xyz, scene_atr, scene, center_CLT, 1); // full single
			final long b0_t2 = System.nanoTime();
			OpticalFlow.transformToScenePxPyD(null, sel_disparity, scene_xyz, scene_atr, scene, center_CLT); // selected multi
			final long b0_t3 = System.nanoTime();
			OpticalFlow.transformToScenePxPyD(null, sel_disparity, scene_xyz, scene_atr, scene, center_CLT, 1); // selected single
			final long b0_t4 = System.nanoTime();
			System.out.println(String.format(
					"CuasPoseRT B0 grid-transform (one-shot, warm): full grid defined=%d selected=%d;"+
					" full multi=%.3f ms, full single=%.3f ms; selected-only multi=%.3f ms, selected-only single=%.3f ms"+
					" (threads=%d; churn ~= multi - single/threads; full-grid waste ~= full - selected)",
					n_def, n_sel, (b0_t1-b0_t0)*1e-6, (b0_t2-b0_t1)*1e-6,
					(b0_t3-b0_t2)*1e-6, (b0_t4-b0_t3)*1e-6, ImageDtt.THREADS_MAX));
		}
		final TpTask [][] cons_tasks; // the convert task sets, hoisted for the consolidation OOB filter // By Claude on 07/12/2026
		final boolean resident_tasks; // post-offset slots stay native on the JNA MB path // By Codex on 07/15/2026
		profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
		if (b3_ran) {
			// 3-B rung B3: the whole GPU measure chain (task_update -> geometry/
			// convert x2 -> consolidate -> inter-corr -> normalize -> peak) already
			// ran as ONE JNA call inside gpuTaskBuild (attributed to B3_CHAIN above) -
			// only the corr-index/peak D2H and the Java unpack remain below.
			// By Claude on 07/16/2026.
			cons_tasks = b1_tasks;
			resident_tasks = true;
		} else if (b1_built) {
			// 3-B rung B1: tasks are already GPU-built in the resident slots. The
			// Java-side skeleton only carries count/txy/task_size for consolidation
			// and the armed pose_corr export. By Claude on 07/16/2026.
			cons_tasks = b1_tasks;
			// armed pose_corr capture = the explicit diagnostic exception to
			// residency: read the GPU-built PRE-OFFSET streams back and capture the
			// task words RAW (no |511 re-OR - holes must stay 0 for a tol-0 replay).
			if (PoseCorrExport.isArmed()) {
				gpuQuad.updateTasks(cons_tasks[0], false, 0);
				gpuQuad.updateTasks(cons_tasks[1], false, 1);
				PoseCorrExport.iterTasksPre(clt_parameters, image_dtt, gpuQuad, scene, cons_tasks, false);
			}
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.TASK_BUILD_SORT, profileStart);
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			image_dtt.interCorrTDResident(
					1,                          // erase_clt_first: NaN outside the task set
					clt_parameters.gpu_sigma_r,
					clt_parameters.gpu_sigma_b,
					clt_parameters.gpu_sigma_g,
					clt_parameters.gpu_sigma_m,
					scene.isMonochrome() ? 1.0 : clt_parameters.gpu_sigma_rb_corr,
					clt_parameters.getGpuCorrSigma(scene.isMonochrome()),
					clt_parameters.getGpuCorrLoGSigma(scene.isMonochrome()),
					debugLevel);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CONVERT_GEOMETRY, profileStart);
			resident_tasks = true;
		} else if (mb_vectors != null) {
			// Motion-blur compensation (lean v2, the certified CuasRender chain): two task
			// sets, double convert (positive SET + negative shifted SUBTRACT), erase-first
			// so undefined tiles reach downstream (consolidation, renders) as NaN.
			// By Claude on 07/05/2026.
			final TpTask [][] tp_tasks2 = GpuQuad.setInterTasksMotionBlur(
					scene.getNumSensors(),
					scene.getGeometryCorrection().getSensorWH()[0],
					false,                      // GPU calculates port coordinates
					scene_pXpYD,
					selection,                  // only the calibrated tile selection
					mb_tau,
					mb_max_gain,
					mb_vectors,
					scene.getGeometryCorrection(),
					disparity_corr,
					margin,
					null,                       // valid_tiles
					ImageDtt.THREADS_MAX);
			if ((tp_tasks2 == null) || (tp_tasks2[0] == null) || (tp_tasks2[0].length == 0)) {
				System.out.println("leanMeasure(): no MB tasks for scene "+scene.getImageName());
				return null;
			}
			sortTasksByTile(tp_tasks2[0]); // fixed task-stream order (3-A3, GPU static shape) // By Claude on 07/13/2026
			sortTasksByTile(tp_tasks2[1]);
			cons_tasks = tp_tasks2; // By Claude on 07/12/2026
			// pose_corr kernel-test capture (checkpoint 1, roadmap 3-C rung 1): the task
			// streams exactly as handed to the GPU, BEFORE setTasks/updateTasks mutate them.
			// No-op unless the curt.kernel_test=pose_corr run armed it. By Claude on 07/13/2026.
			PoseCorrExport.iterTasksPre(clt_parameters, image_dtt, gpuQuad, scene, cons_tasks);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.TASK_BUILD_SORT, profileStart);
			// B0 task-build attribution (one-shot): warm multi-thread vs single-thread
			// rebuild (pure CPU, no GPU state). By Claude on 07/16/2026.
			if ((clt_parameters.curt.pose_lma_debug >= 1) && !b0_task_reported) {
				b0_task_reported = true;
				int n_task = 0;
				for (TpTask t : tp_tasks2[0]) if (t != null) n_task++;
				final long b0_t0 = System.nanoTime();
				GpuQuad.setInterTasksMotionBlur(
						scene.getNumSensors(), scene.getGeometryCorrection().getSensorWH()[0],
						false, scene_pXpYD, selection, mb_tau, mb_max_gain, mb_vectors,
						scene.getGeometryCorrection(), disparity_corr, margin, null,
						ImageDtt.THREADS_MAX);
				final long b0_t1 = System.nanoTime();
				GpuQuad.setInterTasksMotionBlur(
						scene.getNumSensors(), scene.getGeometryCorrection().getSensorWH()[0],
						false, scene_pXpYD, selection, mb_tau, mb_max_gain, mb_vectors,
						scene.getGeometryCorrection(), disparity_corr, margin, null, 1);
				final long b0_t2 = System.nanoTime();
				System.out.println(String.format(
						"CuasPoseRT B0 task-build (one-shot, warm): tasks=%d (x2 MB sets);"+
						" multi=%.3f ms, single=%.3f ms (threads=%d; sort excluded - timed stage includes it)",
						n_task, (b0_t1-b0_t0)*1e-6, (b0_t2-b0_t1)*1e-6, ImageDtt.THREADS_MAX));
			}
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			image_dtt.interCorrTDMotionBlur(
					clt_parameters.img_dtt,
					1,                          // erase_clt_first: NaN outside the task set
					tp_tasks2,
					null,                       // fcorr_td - not used with mask=0
					clt_parameters.gpu_sigma_r,
					clt_parameters.gpu_sigma_b,
					clt_parameters.gpu_sigma_g,
					clt_parameters.gpu_sigma_m,
					scene.isMonochrome() ? 1.0 : clt_parameters.gpu_sigma_rb_corr,
					clt_parameters.getGpuCorrSigma(scene.isMonochrome()),
					clt_parameters.getGpuCorrLoGSigma(scene.isMonochrome()),
					clt_parameters.corr_red,
					clt_parameters.corr_blue,
					0,                          // sensor_mask_inter = 0: convert only
					ImageDtt.THREADS_MAX,
					debugLevel,
					true);                       // keep post-offset tasks in native slots 0/1
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CONVERT_GEOMETRY, profileStart);
			resident_tasks = gpuQuad.supportsResidentTaskSlots();
		} else {
			final TpTask [] tp_tasks = GpuQuad.setInterTasks(
					scene.getNumSensors(),
					scene.getGeometryCorrection().getSensorWH()[0],
					false,                          // GPU calculates port coordinates
					scene_pXpYD,                    // per-tile pX,pY,disparity
					selection,                      // only the calibrated tile selection
					scene.getGeometryCorrection(),
					disparity_corr,
					margin,
					null,                           // valid_tiles (not needed)
					ImageDtt.THREADS_MAX);
			if ((tp_tasks == null) || (tp_tasks.length == 0)) {
				System.out.println("leanMeasure(): no tasks for scene "+scene.getImageName());
				return null;
			}
			sortTasksByTile(tp_tasks); // fixed task-stream order (3-A3, GPU static shape) // By Claude on 07/13/2026
			cons_tasks = new TpTask [][] {tp_tasks, null}; // no-MB: single set, [1] = null // By Claude on 07/12/2026
			PoseCorrExport.iterTasksPre(clt_parameters, image_dtt, gpuQuad, scene, cons_tasks); // pose_corr capture (armed only) // By Claude on 07/13/2026
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.TASK_BUILD_SORT, profileStart);
			// 2. convert only: LPFs + tasks + offsets + convert_direct, NO correlation (mask=0)
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			image_dtt.interCorrTD(
					clt_parameters.img_dtt,
					tp_tasks,
					null,                           // fcorr_td - not used with mask=0
					clt_parameters.gpu_sigma_r,
					clt_parameters.gpu_sigma_b,
					clt_parameters.gpu_sigma_g,
					clt_parameters.gpu_sigma_m,
					scene.isMonochrome() ? 1.0 : clt_parameters.gpu_sigma_rb_corr,
					clt_parameters.getGpuCorrSigma(scene.isMonochrome()),
					clt_parameters.getGpuCorrLoGSigma(scene.isMonochrome()),
					clt_parameters.corr_red,
					clt_parameters.corr_blue,
					0,                              // sensor_mask_inter = 0: convert only, return before correlate
					ImageDtt.THREADS_MAX,
					debugLevel);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CONVERT_GEOMETRY, profileStart);
			// DEBUG (img_out): re-convert with erase_clt=1 so non-task tiles are NaN (interCorrTD
			// converts with erase=-1, leaving the PREVIOUS scene's tiles outside the selection -
			// harmless for correlation, ghosts in a render). Same LPFs/tasks/offsets - identical
			// task-tile content, only the background differs. By Claude on 07/04/2026.
			// Timed as DEBUG_FETCH, not CONVERT_GEOMETRY (rung A). By Claude on 07/16/2026.
			if (img_out != null) {
				profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
				gpuQuad.execConvertDirect(false, null, 1);
				if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.DEBUG_FETCH, profileStart);
			}
			resident_tasks = false;
		}
		// 3. the consolidation bridge: GPU v1 chain (JNA backend, on-device, OOB-filtered)
		// with the CPU bridge as the JCuda-backend fallback - see CuasTD.consolidateToSlot0.
		// By Claude on 07/12/2026 (was the unconditional CPU bridge).
		// The TRUE weighted average is fed to the correlator (NOT scaled to the oracle's
		// 16-pair sum): the sum-scaling experiment (LEAN-07) made peaks WIDER (sqrt_l0
		// 3.22 -> 5.58, corr RMS 0.82 -> 1.005) - at 16x signal-to-fat-zero the normalize
		// amplifies the blur-decorrelated high frequencies. Peak width here is BLUR-
		// dominated, not fz-dominated (NOMB oracle 3.58 ~ lean 3.22 at 16x different
		// amplitude); the fat zero regularizes usefully at the average's operating point.
		// By Claude on 07/04/2026, falsified-and-reverted per LEAN-07.
		// pose_corr capture: the SAME arrays after updateTasks() filled the GPU-computed
		// offsets - the exact stream the consolidation flattens. By Claude on 07/13/2026.
		// Export is the explicit diagnostic exception to residency: read both slots
		// only when a pose_corr capture is armed. By Codex on 07/15/2026.
		if (b3_ran) { // rung B3: consolidation ran in-chain; keep the misaligned-pair warning parity // By Claude on 07/16/2026
			if (b3_stats[2] != 0) {
				System.out.println("CuasPoseRT B3 measure cycle: WARNING - "+b3_stats[2]+
						" misaligned MB pairs, "+b3_stats[0]+" pairs -> "+b3_stats[1]+" tiles");
			}
		} else {
			if (resident_tasks && PoseCorrExport.isArmed()) {
				gpuQuad.updateTasks(cons_tasks[0], false, 0);
				gpuQuad.updateTasks(cons_tasks[1], false, 1);
			}
			PoseCorrExport.iterTasksPost(scene, cons_tasks);
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			if (resident_tasks) CuasTD.consolidateResidentToSlot0(gpuQuad, cons_tasks,
					clt_parameters.curt.oob_soft, clt_parameters.curt.oob_hard);
			else CuasTD.consolidateToSlot0(gpuQuad, cons_tasks, // By Claude on 07/12/2026
					clt_parameters.curt.oob_soft, clt_parameters.curt.oob_hard);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CONSOLIDATE, profileStart);
		}
		// DEBUG (img_out): render the composite the correlator sees (the weighted average).
		// Timed as DEBUG_FETCH: debug-save data motion, ~20-24 ms/call in the 00:25:48
		// profile, was hiding in "leanMeasure other" (rung A, design 2026-07-16).
		// By Claude on 07/16/2026.
		if (img_out != null) {
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			img_out[0] = com.elphel.imagej.cuas.CuasMotion.perSensorImagesFromTD(gpuQuad, false)[0];
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.DEBUG_FETCH, profileStart);
		}
		// 4. single conj-multiply: only sensor 0 (holding the average) vs the center ref
		final double [] col_weights = scene.isMonochrome() ?
				(new double [] {1.0}) :
				(new double [] {clt_parameters.corr_red/(1.0+clt_parameters.corr_red+clt_parameters.corr_blue),
						clt_parameters.corr_blue/(1.0+clt_parameters.corr_red+clt_parameters.corr_blue),
						1.0/(1.0+clt_parameters.corr_red+clt_parameters.corr_blue)});
		if (!b3_ran) { // rung B3: correlation ran in-chain // By Claude on 07/16/2026
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			gpuQuad.setSensorMaskInter(1);
			gpuQuad.execCorr2D_inter_TD(col_weights);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CORRELATE_TD, profileStart);
			// pose_corr capture: checkpoint 1 = the raw TD correlation + packed indices right
			// here, BEFORE fat-zero normalization (rung 2's boundary). By Claude on 07/13/2026.
			PoseCorrExport.iterCorr(gpuQuad, scene, col_weights);
		}
		// 5. FZ-normalize IN PLACE (rung 2, 07/13/2026): corr2D_normalize runs on the TD
		// buffer where execCorr2D_inter_TD left it - no TD round trip (bit-identical:
		// per-tile independent kernel, uniform weight 1.0 == null weights - see
		// TDCorrTile.convertTDtoPDInPlace/fetchNormalizedPD).
		profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
		final int [] corr_indices = gpuQuad.getCorrIndices(); // small D2H: peak-row mapping + count refresh
		if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.CORR_INDEX_D2H, profileStart);
		if (!b3_ran) { // rung B3: normalize ran in-chain // By Claude on 07/16/2026
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			gpuQuad.execCorr2D_normalize(
					false,                        // per-pair buffer (the inter output)
					clt_parameters.curt.fz_inter, // RT-decoupled INTER fat zero (was erroneously the INTRA getGpuFatZero) // By Claude on 07/05/2026
					null,                         // weights: null == uniform 1.0
					GPUTileProcessorDttSize() - 1);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.NORMALIZE, profileStart);
		}
		// 6. peak + eigen per tile - the frozen 15-arg getMaxXYCmEig contract (Andrey's
		// rulings 07/13-14/2026: closed-form 2x2 eigen; recentering + envelope de-bias as
		// runtime params; FLOAT kernel in production - real-case float-vs-double dx/dy
		// <= 7.2e-7 px, ratified 07/14). corr2D_peak_eig_f measures on the GPU and only
		// [n_corr][8] peak floats cross D2H - the PD download happens ONLY for debug
		// rendering or the pose_corr capture. NO JCuda fallback (Andrey's ruling 07/14:
		// keep fallbacks only where free) - a backend without the kernel fails loudly;
		// the double kernel + the Java oracle remain as VALIDATION tiers (tests/export).
		// By Claude on 07/14/2026 (was the unconditional PD D2H + Java loop).
		if (!b3_ran) { // rung B3: debias upload is part of b3CycleSetup on batched cycles // By Claude on 07/16/2026
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			final double [] debias = (clt_parameters.curt.pose_debias > 0) ?
					windowAutocorrDebias(GPUTileProcessorDttSize(), clt_parameters.curt.pose_debias) : null;
			float [] fdebias = null;
			if (debias != null) {
				fdebias = new float [debias.length];
				for (int k = 0; k < debias.length; k++) fdebias[k] = (float) debias[k];
			}
			gpuQuad.setPeakDebias(fdebias); // upload or clear (base/JCuda: no-op false)
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.DEBIAS_UPLOAD, profileStart);
		}
		profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
		final float [] gpu_peaks = b3_ran ?
				gpuQuad.fetchCorr2DPeaks(corr_indices.length) : // rung B3: kernel ran in-chain, D2H only // By Claude on 07/16/2026
				gpuQuad.execCorr2DPeaks(
				0xff,                             // the sum slot (the consolidated average x center)
				clt_parameters.imp.eig_min_abs,   // abs_min
				clt_parameters.imp.eig_min_rel,   // rel_min
				clt_parameters.imp.min_str_sum,   // min_peak (single channel = the "sum")
				clt_parameters.imp.eig_sub_frac,  // pedestal suppression (see the eigen-starvation note in git 59f4182d)
				clt_parameters.curt.pose_recenter,// recentering passes (0 = baseline single-pass)
				clt_parameters.imp.eig_sub_frac1, // pedestal suppression during recentering
				clt_parameters.imp.eig_scale_axes,// recenter window: scale eigen-ellipse half-axes
				clt_parameters.imp.eig_inc_axes,  // recenter window: add to half-axes, pix
				true);                            // float = production (double stays the validation tier)
		if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.PEAK_D2H, profileStart);
		if (gpu_peaks == null) { // no fallback by ruling - fail loudly, not silently slower
			System.out.println("leanMeasure(): GPU peak kernel unavailable (JCuda backend?) - "+
					"the lean pose chain requires the JNA backend (-Dtp.backend=jna)");
			return null;
		}
		// 3-B rung B3 one-shot oracle: the batched chain ran FIRST this cycle (inside
		// gpuTaskBuild) and captured its indices+peaks; the granular chain just re-ran
		// the identical sequence - every (tile,pair) row must match BIT-EXACT.
		// ORDER-INDEPENDENT by packed corr index (the rung-1 rule): correlate2D_inter
		// assigns rows via atomicAdd, so the ROW ORDER is legitimately nondeterministic
		// between runs - only the per-index content is contractual (all consumers map
		// rows through corr_indices). The 07/16 22:09 gate run FALSE-FAILed on a naive
		// positional compare while all 497 poses stayed byte-identical - fixed here.
		// By Claude on 07/16/2026.
		if ((b3_oracle_peaks != null) && !b3_oracle_reported) {
			final java.util.HashMap<Integer, Integer> oracle_rows = new java.util.HashMap<>();
			for (int n = 0; n < b3_oracle_indices.length; n++) oracle_rows.put(b3_oracle_indices[n], n);
			int missing = 0; // granular rows whose packed index the batched run never emitted
			long mism = 0;   // bit-mismatched floats among index-matched rows
			final int row_floats = GpuQuad.PEAK_ROW_FLOATS;
			for (int n = 0; n < corr_indices.length; n++) {
				final Integer on = oracle_rows.remove(corr_indices[n]);
				if (on == null) { missing++; continue; }
				for (int k = 0; k < row_floats; k++) {
					if (Float.floatToRawIntBits(gpu_peaks[n * row_floats + k]) !=
							Float.floatToRawIntBits(b3_oracle_peaks[on * row_floats + k])) mism++;
				}
			}
			final int extra = oracle_rows.size(); // batched-only indices left unmatched
			final boolean oracle_pass = (missing == 0) && (extra == 0) && (mism == 0);
			System.out.println(String.format(
					"CuasPoseRT B3 batched-vs-granular oracle (one-shot, order-independent by packed index):"+
					" %d corr rows, unmatched granular=%d batched=%d, peak bit-mismatches=%d,"+
					" consolidation stats=(%d, %d, %d) -> %s",
					corr_indices.length, missing, extra, mism,
					b3_oracle_stats[0], b3_oracle_stats[1], b3_oracle_stats[2],
					oracle_pass ? "PASS" : "FAIL"));
			if (!oracle_pass) { // fail-safe: a REAL divergence must never drive production
				b3_oracle_failed = true;
				System.out.println("CuasPoseRT: B3 batched chain DISABLED for this run (oracle FAIL) - staying granular");
			}
			b3_oracle_reported = true;
			b3_oracle_indices = null; b3_oracle_peaks = null; b3_oracle_stats = null;
		}
		if (!peaks_path_reported) {
			peaks_path_reported = true;
			System.out.println("CuasPoseRT: GPU peak kernel active (corr2D_peak_eig_f, float; "+
					"recenter="+clt_parameters.curt.pose_recenter+", debias="+clt_parameters.curt.pose_debias+
					") - PD stays on the GPU");
		}
		// PD crosses only when actually consumed. Timed as DEBUG_FETCH: debug-save data
		// motion (~2-4 ms/call), was hiding in "leanMeasure other" (rung A). By Claude on 07/16/2026.
		double [][] corr_pd = null;
		if ((corr_pd_out != null) || PoseCorrExport.isArmed()) {
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			corr_pd = TDCorrTile.fetchNormalizedPD(gpuQuad, corr_indices);
			if (rtProfile != null) measureAccounted += rtProfile.addElapsed(RtPoseProfile.DEBUG_FETCH, profileStart);
			if (corr_pd == null) {
				System.out.println("leanMeasure(): fetchNormalizedPD returned null for scene "+scene.getImageName());
				return null;
			}
		}
		// pose_corr capture: checkpoint 2 = the FZ-normalized PD (in-place-normalize tol-0
		// target) + the frozen 15-arg peak-contract oracle on every PD tile + the production
		// float-kernel peaks (the future tol-0 peak tier). Armed-only no-ops. By Claude on 07/13-14/2026.
		PoseCorrExport.iterPd(clt_parameters, gpuQuad, scene, corr_pd);
		PoseCorrExport.iterGpuPeaks(scene, gpu_peaks);
		if (corr_pd_out != null) corr_pd_out[0] = corr_pd; // debug rendering (-POSE-RT-CORR2D) // By Claude on 07/04/2026
		// (img_out is rendered in step 3, BEFORE the sum scaling - the true weighted average) // By Claude on 07/04/2026
		profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
		final double [][] centers =    new double [num_pix][];
		final double [][] vector_XYS = new double [num_pix][];
		final double [][] eigen =      new double [num_pix][];
		int num_meas = 0;
		// peak rows align 1:1 with corr_indices; sum slots carry the measurement,
		// valid==0 = kernel reject (weak/NaN/degenerate, as the oracle null). The float
		// values are widened to double ONCE here (exact) - the LMA solver stays double.
		for (int n = 0; n < corr_indices.length; n++) {
			if ((corr_indices[n] & ((1 << GPUTileProcessor.CORR_NTILE_SHIFT) - 1)) != 0xff) continue;
			final int i = corr_indices[n] >> GPUTileProcessor.CORR_NTILE_SHIFT;
			if ((i < 0) || (i >= num_pix) || (pXpYD_center[i] == null)) continue;
			final int row = n * GpuQuad.PEAK_ROW_FLOATS;
			if (gpu_peaks[row + GpuQuad.PEAK_ROW_FLOATS - 1] == 0.0f) continue; // rejected
			centers[i] =    pXpYD_center[i];
			vector_XYS[i] = new double [] {gpu_peaks[row], gpu_peaks[row + 1], gpu_peaks[row + 2]};
			eigen[i] =      new double [] {gpu_peaks[row + 3], gpu_peaks[row + 4],
					gpu_peaks[row + 5], gpu_peaks[row + 6]};
			num_meas++;
		}
		if (debugLevel > -2) {
			System.out.println("leanMeasure(): scene "+scene.getImageName()+" measured "+num_meas+" tiles");
		}
		if (num_meas == 0) return null;
		if (rtProfile != null) {
			measureAccounted += rtProfile.addElapsed(RtPoseProfile.PEAK_UNPACK, profileStart);
			final long measureTotal = System.nanoTime() - measureStart;
			rtProfile.add(RtPoseProfile.MEASURE_TOTAL, measureTotal);
			rtProfile.add(RtPoseProfile.MEASURE_OTHER, Math.max(0L, measureTotal - measureAccounted));
		}
		return new double [][][] {centers, vector_XYS, eigen};
	}

	// once-per-program console note that the GPU peak path is live // By Claude on 07/14/2026
	private static boolean peaks_path_reported = false;
	// once-per-program console note for the rung-3 normal-equation product path
	private static boolean lma_products_path_reported = false;
	// once-per-program console note for production getFxDerivs() on CUDA
	private static boolean pose_fx_path_reported = false;
	// once-per-program console note for resident float preparation/reduction/candidate
	private static boolean pose_lma_step_path_reported = false;
	private static boolean pose_prepare_path_reported = false; // rung C1 // By Claude on 07/17/2026
	// one raw-pixel/J comparison of the float oracle against the current double geometry
	private static boolean float_jacobian_oracle_reported = false;
	// 3-B rung B0 (design 2026-07-16): one-shot attribution micro-measurements at
	// pose_lma_debug>=1 - re-run the CPU front-end stages under controlled variants
	// (single-thread, selection-only grid) to split thread-array churn / full-grid
	// waste / irreducible work BEFORE B1 deletes the stages. The extra runs happen
	// ONCE per program on the first measured cycle and land in "leanMeasure other"
	// for that one call only. By Claude on 07/16/2026.
	private static boolean b0_grid_reported = false;
	private static boolean b0_task_reported = false;
	private static boolean b2_mb_reported = false; // B2 MB-descriptor one-shot oracle (was b0_mb_reported) // By Claude on 07/16/2026
	// 3-B rung B1 (design 2026-07-16): resident GPU task build. By Claude on 07/16/2026.
	private static boolean task_build_path_reported = false; // once-per-program "path active" note
	private static boolean b1_task_oracle_reported = false;  // one-shot GPU vs Java-double vs float-serial compare
	// 3-B rung B3 (design 2026-07-16): single measure-chain entry. The one-shot
	// oracle runs the batched chain FIRST on the arm cycle (idempotent: every
	// stage is a deterministic function of the resident state + pose), captures
	// its corr indices + peaks, lets the granular chain re-run the identical
	// sequence, and compares BIT-EXACT at the granular peak fetch. Batched
	// production cycles start only after the oracle reported (or pose_lma_debug
	// < 1). By Claude on 07/16/2026.
	private static boolean b3_path_reported = false;   // once-per-program "path active" note
	private static boolean b3_oracle_reported = false; // one-shot batched-vs-granular bit compare
	private static boolean b3_oracle_failed = false;   // fail-safe: FAIL keeps the run granular // By Claude on 07/16/2026
	private static int []   b3_oracle_indices = null;  // oracle captures (arm cycle only)
	private static float [] b3_oracle_peaks = null;
	private static int []   b3_oracle_stats = null;

	/**
	 * 3-B rung B3: make the batched chain's inputs resident before the single
	 * entry - gc/cv (execSetTilesOffsets used to re-upload them inside the
	 * chain), the four LPF/LoG constant tables (interCorrTDResident used to),
	 * the peak de-bias map (leanMeasure step 6 used to), the inter sensor mask,
	 * and the per-scene stage-argument descriptor. All values are identical to
	 * what the granular path uploads every cycle - byte-identical by data.
	 * By Claude on 07/16/2026.
	 */
	private static void b3CycleSetup(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       scene,
			final GpuQuad       gpuQuad,
			final int           num_tasks,
			final int           task_size,
			final double        disparity_corr,
			final float         min_px,
			final float         max_px,
			final float         min_py,
			final float         max_py,
			final int           task_code_main,
			final int           task_code_sub,
			final double        dx_main,
			final double        dy_main,
			final double        dx_sub,
			final double        dy_sub,
			final double        scale_main,
			final double        scale_sub) {
		gpuQuad.uploadPoseCycleGeometry(); // gc (cached) + cv - same bytes the granular chain re-uploads
		image_dtt.setInterCorrLpfs(
				clt_parameters.gpu_sigma_r,
				clt_parameters.gpu_sigma_b,
				clt_parameters.gpu_sigma_g,
				clt_parameters.gpu_sigma_m,
				scene.isMonochrome() ? 1.0 : clt_parameters.gpu_sigma_rb_corr,
				clt_parameters.getGpuCorrSigma(scene.isMonochrome()),
				clt_parameters.getGpuCorrLoGSigma(scene.isMonochrome()),
				-3);
		final double [] debias = (clt_parameters.curt.pose_debias > 0) ?
				windowAutocorrDebias(GPUTileProcessorDttSize(), clt_parameters.curt.pose_debias) : null;
		float [] fdebias = null;
		if (debias != null) {
			fdebias = new float [debias.length];
			for (int k = 0; k < debias.length; k++) fdebias[k] = (float) debias[k];
		}
		gpuQuad.setPeakDebias(fdebias);
		gpuQuad.setSensorMaskInter(1); // the consolidated average in the cam-0 slot
		final double [] col_weights = scene.isMonochrome() ?
				(new double [] {1.0}) :
				(new double [] {clt_parameters.corr_red/(1.0+clt_parameters.corr_red+clt_parameters.corr_blue),
						clt_parameters.corr_blue/(1.0+clt_parameters.corr_red+clt_parameters.corr_blue),
						1.0/(1.0+clt_parameters.corr_red+clt_parameters.corr_blue)});
		gpuQuad.setPoseCycleConfig(
				num_tasks, task_size,
				(float) disparity_corr,
				min_px, max_px, min_py, max_py,
				task_code_main, task_code_sub,
				(float) dx_main, (float) dy_main, (float) dx_sub, (float) dy_sub,
				(float) scale_main, (float) scale_sub,
				clt_parameters.curt.oob_soft, clt_parameters.curt.oob_hard,
				gpuQuad.getSensorMaskInter(), col_weights,
				clt_parameters.curt.fz_inter, GPUTileProcessorDttSize() - 1,
				0xff,                             // the sum slot (the consolidated average x center)
				clt_parameters.imp.eig_min_abs,
				clt_parameters.imp.eig_min_rel,
				clt_parameters.imp.min_str_sum,
				clt_parameters.imp.eig_sub_frac,
				clt_parameters.curt.pose_recenter,
				clt_parameters.imp.eig_sub_frac1,
				clt_parameters.imp.eig_scale_axes,
				clt_parameters.imp.eig_inc_axes,
				true);                            // float peak kernel = production (ratified 07/14)
	}

	/**
	 * Per-sequence state of the resident GPU task build (rung B1): the sorted task
	 * template (packed txy + reference centers per selected tile), the Java-side
	 * skeleton (carries count/txy/task_size for consolidation and the armed
	 * pose_corr export - the live task content is GPU-resident), and the
	 * configuration-register bookkeeping (which scene's camera block + skeleton
	 * are resident). Keyed by the per-sequence array/object identities.
	 * By Claude on 07/16/2026.
	 */
	private static final class GpuTaskTemplate {
		final boolean []  selectionKey;
		final double [][] centersKey;
		final GpuQuad     gpuKey;
		final int         count;
		final int []      map;      // packed txy: tx | (ty<<16), (tileY,tileX)-ascending
		final float []    centers;  // reference px,py,disparity per task
		final TpTask [][] skeleton; // [2][count]
		IntersceneLmaFloat.Camera refCamera = null; // captured once per sequence (after setupERS)
		String uploadedScene = null; // scene whose camera block + skeleton slots are current
		GpuTaskTemplate(boolean [] selectionKey, double [][] centersKey, GpuQuad gpuKey,
				int count, int [] map, float [] centers, TpTask [][] skeleton) {
			this.selectionKey = selectionKey;
			this.centersKey = centersKey;
			this.gpuKey = gpuKey;
			this.count = count;
			this.map = map;
			this.centers = centers;
			this.skeleton = skeleton;
		}
	}
	private static GpuTaskTemplate gpu_task_template = null;

	/**
	 * Shared per-scene measure setup of the resident GPU task build (rung B1
	 * steps 1-5, extracted verbatim for the D4 scene-DP entry): uniform-MB
	 * verification, per-sequence template/skeleton, per-scene configuration
	 * registers (camera capture + skeleton slots), the 6-float uniform-MB
	 * descriptor (exact setInterTasksMotionBlur double math) and the margin
	 * gate bounds. Returns null = fall back to the legacy CPU build.
	 * By Claude on 07/17/2026.
	 */
	private static final class PoseMeasureSetup {
		GpuTaskTemplate tpl;
		IntersceneLmaFloat.Camera sceneCamera; // non-null only when the scene camera block needs upload
		boolean newScene;
		boolean newTemplate;
		int taskCodeMain, taskCodeSub;
		double dxm, dym, dxs, dys, gain, gainSub;
		float minPx, maxPx, minPy, maxPy;
	}
	private static PoseMeasureSetup poseMeasureSetup(
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double [][]   mb_vectors,
			final double        mb_tau,
			final double        mb_max_gain,
			final int           margin,
			final GpuQuad       gpuQuad) {
		// 1. uniform-MB verification: the descriptor is per-scene ONLY if every
		//    selected tile carries the same (broadcast) vector; the degenerate
		//    per-tile fallback of uniformMotionBlur() goes back to the CPU build.
		double mb0 = Double.NaN, mb1 = Double.NaN;
		for (int nTile = 0; nTile < selection.length; nTile++) {
			if (!selection[nTile] || (pXpYD_center[nTile] == null)) continue;
			final double vx = mb_vectors[0][nTile];
			final double vy = mb_vectors[1][nTile];
			if (Double.isNaN(vx) || Double.isNaN(vy)) return null; // NaN would drop tiles per-tile
			if (Double.isNaN(mb0)) { mb0 = vx; mb1 = vy; }
			else if ((vx != mb0) || (vy != mb1)) return null;      // not uniform
		}
		if (Double.isNaN(mb0)) return null; // no selected tiles with centers
		// 2. per-sequence template + skeleton (cached by array/object identity)
		GpuTaskTemplate tpl = gpu_task_template;
		if ((tpl == null) || (tpl.selectionKey != selection) ||
				(tpl.centersKey != pXpYD_center) || (tpl.gpuKey != gpuQuad)) {
			final int sensor_w = scene.getGeometryCorrection().getSensorWH()[0];
			final int tilesX = sensor_w / GPUTileProcessor.DTT_SIZE;
			final int task_code = (1 << GPUTileProcessor.TASK_CORR_EN) | (1 << GPUTileProcessor.TASK_INTER_EN);
			int count = 0;
			for (int nTile = 0; nTile < selection.length; nTile++) {
				if (selection[nTile] && (pXpYD_center[nTile] != null) &&
						!Double.isNaN(pXpYD_center[nTile][2])) count++;
			}
			if (count == 0) return null;
			final int []   map =     new int [count];
			final float [] centers = new float [3 * count];
			final TpTask [][] skeleton = new TpTask [2][count];
			int i = 0;
			for (int nTile = 0; nTile < selection.length; nTile++) { // nTile-ascending == (tileY,tileX) sort (3-A3)
				if (!selection[nTile] || (pXpYD_center[nTile] == null) ||
						Double.isNaN(pXpYD_center[nTile][2])) continue;
				final int tileX = nTile % tilesX;
				final int tileY = nTile / tilesX;
				map[i] = tileX | (tileY << 16);
				for (int k = 0; k < 3; k++) centers[3 * i + k] = (float) pXpYD_center[nTile][k];
				for (int set = 0; set < 2; set++) {
					skeleton[set][i] = new TpTask(scene.getNumSensors(), tileX, tileY);
					skeleton[set][i].task = task_code;
				}
				i++;
			}
			center_CLT.getErsCorrection().setupERS(); // reference line tables (transformToScenePxPyD parity)
			tpl = new GpuTaskTemplate(selection, pXpYD_center, gpuQuad,
					count, map, centers, skeleton);
			tpl.refCamera = IntersceneLmaFloat.Camera.capture(center_CLT.getErsCorrection());
			gpu_task_template = tpl;
		}
		// 3. per-scene configuration registers: camera block (after setupERS) +
		//    skeleton slots (re-uploaded per scene: other pipeline stages may have
		//    replaced the task slots between scenes; ~120 KB, once per scene)
		final PoseMeasureSetup ms = new PoseMeasureSetup();
		ms.tpl = tpl;
		ms.newScene = !scene.getImageName().equals(tpl.uploadedScene);
		ms.newTemplate = (tpl.uploadedScene == null);
		if (ms.newScene) {
			scene.getErsCorrection().setupERS(); // per-scene ERS line tables (transformToScenePxPyD parity)
			ms.sceneCamera = IntersceneLmaFloat.Camera.capture(scene.getErsCorrection());
			gpuQuad.setTasks(tpl.skeleton[0], false, false, 0); // sizes slots + |511 task words
			gpuQuad.setTasks(tpl.skeleton[1], false, false, 1);
		}
		// 4. per-scene uniform-MB descriptor - the EXACT setInterTasksMotionBlur
		//    double math (accumulate=false, global_scale=1.0 as the leanMeasure
		//    convenience overload passes them), cast to float at the end like
		//    setCenterXY/setScale would
		final int task_code_or = ((1 << GPUTileProcessor.TASK_CORR_EN) | (1 << GPUTileProcessor.TASK_INTER_EN)) | 511;
		final double min_sub = 1e-12;
		final double min_len = 0.001; // pix
		double dx = mb0;
		double dy = mb1;
		double mb_len = Math.sqrt(dx * dx + dy * dy);
		if (mb_len < min_len) { // no-MB: sub set disabled (task 0 -> |511 = 511)
			ms.dxm = 0.0; ms.dym = 0.0; ms.dxs = 0.0; ms.dys = 0.0;
			ms.gain = 1.0;
			ms.gainSub = -min_sub;
			ms.taskCodeSub = 511;
		} else {
			dx /= mb_len; // unit vector
			dy /= mb_len;
			mb_len *= mb_tau; // now in pixels
			final double mb_len_scale = -Math.log(1.0 - 1.0 / mb_max_gain);
			double mb_offs = 1.0;
			final double min_offs = mb_len_scale * mb_len;
			if (mb_offs < min_offs) {
				mb_offs = min_offs;
			}
			dx *= mb_offs;
			dy *= mb_offs;
			final double exp_offs = Math.exp(-mb_offs / mb_len);
			final double rel_cent = mb_len * (1.0 - (mb_offs / mb_len + 1.0) * exp_offs) / (mb_offs * (1 - exp_offs));
			ms.dxm = -rel_cent * dx;                    // centerXY(main) = projected - rel_cent*d
			ms.dym = -rel_cent * dy;
			ms.dxs = ms.dxm + (1 - rel_cent) * dx;      // centerXY(sub) = main + (1-rel_cent)*d (Java order preserved)
			ms.dys = ms.dym + (1 - rel_cent) * dy;
			ms.gain = 1.0 / (1.0 - exp_offs);
			ms.gainSub = -ms.gain * exp_offs;
			if (ms.gainSub > -min_sub) {
				ms.gainSub = -min_sub;
			}
			ms.taskCodeSub = task_code_or;
		}
		ms.taskCodeMain = task_code_or;
		// 5. margin gate bounds - GPU port-coords mode Java bounds (no WOI tops)
		final int [] wh = scene.getGeometryCorrection().getSensorWH();
		ms.minPx = margin;
		ms.maxPx = wh[0] - 1 - margin;
		ms.minPy = margin;
		ms.maxPy = wh[1] - 1 - margin;
		return ms;
	}

	/**
	 * 3-B rung B1 (design 2026-07-16): build the correlation tasks ON-DEVICE.
	 * Verifies the MB vectors are uniform (the pose_mb_uniform broadcast), builds/
	 * caches the per-sequence template + skeleton, refreshes the per-scene
	 * configuration registers (scene camera block after setupERS + skeleton
	 * re-upload, guarding against other pipeline stages having used the task
	 * slots between scenes), computes the per-scene 6-float uniform-MB descriptor
	 * with the EXACT setInterTasksMotionBlur double math, and launches
	 * pose_task_update to rewrite both resident task slots from the current pose.
	 * Zero per-cycle H2D beyond the 12-float pose vector.
	 *
	 * @return the Java-side skeleton TpTask[2][count] to use as cons_tasks, or
	 *         null to fall back to the legacy CPU build (non-uniform/NaN MB).
	 * By Claude on 07/16/2026.
	 */
	private static TpTask [][] gpuTaskBuild(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double []     center_disparity,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double []     scene_xyz,
			final double []     scene_atr,
			final int           margin,
			final double [][]   mb_vectors,
			final double        mb_tau,
			final double        mb_max_gain,
			final double        disparity_corr,
			final GpuQuad       gpuQuad,
			final int []        b3_stats, // rung B3: non-null = run the BATCHED single-entry chain (fills consolidation stats) // By Claude on 07/16/2026
			final int           debugLevel) {
		// Steps 1-5 extracted to poseMeasureSetup() (shared with the D4 scene-DP
		// entry): uniform-MB check, template/skeleton, per-scene registers, the
		// uniform-MB descriptor and the margin bounds. By Claude on 07/17/2026.
		final PoseMeasureSetup ms = poseMeasureSetup(center_CLT, scene, pXpYD_center,
				selection, mb_vectors, mb_tau, mb_max_gain, margin, gpuQuad);
		if (ms == null) return null;
		final GpuTaskTemplate tpl = ms.tpl;
		final boolean new_scene = ms.newScene;
		final boolean new_template = ms.newTemplate;
		final IntersceneLmaFloat.Camera scene_camera = ms.sceneCamera;
		final int task_code_or = ms.taskCodeMain;
		final int task_code_sub = ms.taskCodeSub;
		final double dxm = ms.dxm, dym = ms.dym, dxs = ms.dxs, dys = ms.dys;
		final double gain = ms.gain, gain_sub = ms.gainSub;
		final float min_px = ms.minPx;
		final float max_px = ms.maxPx;
		final float min_py = ms.minPy;
		final float max_py = ms.maxPy;
		// 6. launch: template on rebuild, camera block per scene, pose every call
		//    (48 B - the Java-tracked measure pose; B3/DP chain will drop it)
		final float [] ref_xyz =   IntersceneLmaFloat.toFloat3(ZERO3);
		final float [] ref_atr =   IntersceneLmaFloat.toFloat3(ZERO3);
		final float [] scene_xyzf = IntersceneLmaFloat.toFloat3(scene_xyz);
		final float [] scene_atrf = IntersceneLmaFloat.toFloat3(scene_atr);
		// 3-B rung B3 (design 2026-07-16): the batched single-entry chain replaces
		// steps 6-7 AND the whole granular GPU sequence of leanMeasure (geometry/
		// convert x2, consolidate, inter-corr, normalize, peak) with ONE JNA call.
		// The one-shot oracle (pose_lma_debug>=1, first eligible cycle) runs the
		// batched chain here, captures indices+peaks, then falls through to the
		// legacy launch so the granular chain re-runs the identical sequence -
		// leanMeasure compares the two captures bit-exact at its peak fetch.
		// By Claude on 07/16/2026.
		final boolean b3_batched = (b3_stats != null);
		final boolean b3_oracle = !b3_batched && !b3_oracle_reported &&
				(clt_parameters.curt.pose_lma_debug >= 1) &&
				gpuQuad.supportsPoseMeasureCycle() && !PoseCorrExport.isArmed();
		if (b3_batched || b3_oracle) {
			b3CycleSetup(clt_parameters, image_dtt, scene, gpuQuad,
					tpl.count, tpl.skeleton[0][0].getSize(),
					disparity_corr, min_px, max_px, min_py, max_py,
					task_code_or, task_code_sub,
					dxm, dym, dxs, dys, gain, gain_sub);
			final int [] stats = b3_batched ? b3_stats : new int [3];
			gpuQuad.execPoseMeasureCycle(
					new_template ? tpl.refCamera : null,
					scene_camera,                     // null = keep resident (same scene)
					ref_xyz, ref_atr, scene_xyzf, scene_atrf,
					new_template ? tpl.map : null,
					new_template ? tpl.centers : null,
					stats);
			if (!b3_path_reported) {
				b3_path_reported = true;
				System.out.println("CuasPoseRT: B3 batched measure chain active (single "+
						"tp_proc_exec_pose_measure_cycle entry: task_update + geometry/convert x2 + "+
						"consolidate + inter-corr + normalize + peak; per-cycle D2H = corr indices + peaks)");
			}
			if (b3_oracle) { // capture for the end-of-granular-chain bit compare
				b3_oracle_indices = gpuQuad.getCorrIndices();
				b3_oracle_peaks = gpuQuad.fetchCorr2DPeaks(
						(b3_oracle_indices != null) ? b3_oracle_indices.length : 0);
				b3_oracle_stats = stats;
				// fall through: the legacy launch + granular chain re-run the identical sequence
			} else {
				if (new_scene) tpl.uploadedScene = scene.getImageName();
				if (stats[2] != 0) { // parity with execConsolidateResidentTasksTD's warning
					System.out.println("CuasPoseRT B3 measure cycle: WARNING - "+stats[2]+
							" misaligned MB pairs, "+stats[0]+" pairs -> "+stats[1]+" tiles");
				}
				return tpl.skeleton;
			}
		}
		final boolean built = gpuQuad.execPoseTaskUpdate(
				new_template ? tpl.refCamera : null,
				scene_camera,                     // null = keep resident (same scene)
				ref_xyz, ref_atr, scene_xyzf, scene_atrf,
				new_template ? tpl.map : null,
				new_template ? tpl.centers : null,
				tpl.count,
				(float) disparity_corr,
				min_px, max_px, min_py, max_py,
				task_code_or, task_code_sub,
				(float) dxm, (float) dym, (float) dxs, (float) dys,
				(float) gain, (float) gain_sub);
		if (!built) return null; // base/JCuda backend - legacy CPU build
		if (new_scene) tpl.uploadedScene = scene.getImageName();
		if (!task_build_path_reported) {
			task_build_path_reported = true;
			System.out.println("CuasPoseRT: resident GPU task build active (pose_task_update: "+
					tpl.count+" template tasks, per-cycle H2D = 12-float pose only; "+
					"grid transform + CPU task build + task upload eliminated on the lean path)");
		}
		// 7. one-shot oracle (pose_lma_debug>=1): build the tasks BOTH legacy ways
		//    and compare - GPU vs the Java double build (the D3-ratified <=1e-5 px
		//    field compare) and GPU vs the serial float clone (buildTasks)
		if ((clt_parameters.curt.pose_lma_debug >= 1) && !b1_task_oracle_reported) {
			b1_task_oracle_reported = true;
			b1TaskOracle(clt_parameters, center_CLT, scene, center_disparity, pXpYD_center,
					selection, scene_xyz, scene_atr, margin, mb_vectors, mb_tau, mb_max_gain,
					disparity_corr, gpuQuad, tpl,
					min_px, max_px, min_py, max_py, task_code_or, task_code_sub,
					(float) dxm, (float) dym, (float) dxs, (float) dys,
					(float) gain, (float) gain_sub);
		}
		return tpl.skeleton;
	}

	/**
	 * One-shot B1 task-build oracle (pose_lma_debug>=1, first GPU-built cycle):
	 * reads the GPU-built pre-offset streams back and compares them against
	 * (a) the legacy Java DOUBLE build (transformToScenePxPyD +
	 * setInterTasksMotionBlur, txy-matched - the D3-ratified <=1e-5 px field
	 * compare) and (b) the serial FLOAT clone (IntersceneLmaFloat.buildTasks,
	 * differences are float-scheduling only). Prints one line.
	 * By Claude on 07/16/2026.
	 */
	private static void b1TaskOracle(
			final CLTParameters clt_parameters,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double []     center_disparity,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double []     scene_xyz,
			final double []     scene_atr,
			final int           margin,
			final double [][]   mb_vectors,
			final double        mb_tau,
			final double        mb_max_gain,
			final double        disparity_corr,
			final GpuQuad       gpuQuad,
			final GpuTaskTemplate tpl,
			final float min_px, final float max_px, final float min_py, final float max_py,
			final int task_code_main, final int task_code_sub,
			final float dxm, final float dym, final float dxs, final float dys,
			final float scale_main, final float scale_sub) {
		// GPU-built pre-offset streams (diagnostic readback)
		final TpTask [] gpu0 = new TpTask [tpl.count];
		final TpTask [] gpu1 = new TpTask [tpl.count];
		gpuQuad.updateTasks(gpu0, false, 0);
		gpuQuad.updateTasks(gpu1, false, 1);
		// legacy Java DOUBLE build at the same pose
		final double [][] scene_pXpYD = OpticalFlow.transformToScenePxPyD(
				null, center_disparity, scene_xyz, scene_atr, scene, center_CLT);
		final TpTask [][] cpu = GpuQuad.setInterTasksMotionBlur(
				scene.getNumSensors(), scene.getGeometryCorrection().getSensorWH()[0],
				false, scene_pXpYD, selection, mb_tau, mb_max_gain, mb_vectors,
				scene.getGeometryCorrection(), disparity_corr, margin, null,
				ImageDtt.THREADS_MAX);
		double max_d_center = 0.0, max_d_disp = 0.0;
		int matched = 0, gpu_only = 0, cpu_only = 0;
		if ((cpu != null) && (cpu[0] != null)) {
			final HashMap<Integer,Integer> cpu_index = new HashMap<>();
			for (int j = 0; j < cpu[0].length; j++) {
				if (cpu[0][j] != null) cpu_index.put(cpu[0][j].getTileX() | (cpu[0][j].getTileY() << 16), j);
			}
			for (int i = 0; i < tpl.count; i++) {
				final boolean gpu_present = (gpu0[i].task != 0);
				final Integer j = cpu_index.get(tpl.map[i]);
				if (gpu_present && (j != null)) {
					matched++;
					max_d_center = Math.max(max_d_center, Math.abs(gpu0[i].getCenterXY()[0] - cpu[0][j].getCenterXY()[0]));
					max_d_center = Math.max(max_d_center, Math.abs(gpu0[i].getCenterXY()[1] - cpu[0][j].getCenterXY()[1]));
					max_d_center = Math.max(max_d_center, Math.abs(gpu1[i].getCenterXY()[0] - cpu[1][j].getCenterXY()[0]));
					max_d_center = Math.max(max_d_center, Math.abs(gpu1[i].getCenterXY()[1] - cpu[1][j].getCenterXY()[1]));
					max_d_disp = Math.max(max_d_disp, Math.abs(gpu0[i].target_disparity - cpu[0][j].target_disparity));
				} else if (gpu_present) gpu_only++;
				else if (j != null) cpu_only++;
			}
		}
		// serial FLOAT clone of the kernel
		final float [][] oracle = IntersceneLmaFloat.buildTasks(
				tpl.refCamera, IntersceneLmaFloat.Camera.capture(scene.getErsCorrection()),
				IntersceneLmaFloat.toFloat3(ZERO3), IntersceneLmaFloat.toFloat3(ZERO3),
				IntersceneLmaFloat.toFloat3(scene_xyz), IntersceneLmaFloat.toFloat3(scene_atr),
				tpl.map, tpl.centers, TpTask.getSize(scene.getNumSensors()),
				(float) disparity_corr, min_px, max_px, min_py, max_py,
				task_code_main, task_code_sub, dxm, dym, dxs, dys, scale_main, scale_sub);
		final int task_size = TpTask.getSize(scene.getNumSensors());
		double max_d_float = 0.0;
		int hole_mismatch = 0;
		for (int i = 0; i < tpl.count; i++) {
			final int base = i * task_size;
			final boolean gpu_present = (gpu0[i].task != 0);
			final boolean oracle_present = (Float.floatToRawIntBits(oracle[0][base]) != 0);
			if (gpu_present != oracle_present) { hole_mismatch++; continue; }
			if (!gpu_present) continue;
			max_d_float = Math.max(max_d_float, Math.abs(gpu0[i].getCenterXY()[0] - oracle[0][base + 3]));
			max_d_float = Math.max(max_d_float, Math.abs(gpu0[i].getCenterXY()[1] - oracle[0][base + 4]));
			max_d_float = Math.max(max_d_float, Math.abs(gpu1[i].getCenterXY()[0] - oracle[1][base + 3]));
			max_d_float = Math.max(max_d_float, Math.abs(gpu1[i].getCenterXY()[1] - oracle[1][base + 4]));
			max_d_float = Math.max(max_d_float, Math.abs(gpu0[i].target_disparity - oracle[0][base + 2]));
		}
		System.out.println(String.format(
				"CuasPoseRT B1 task oracle (one-shot): %d template tasks; GPU vs Java-double:"+
				" matched=%d max|dCenterXY|=%.3g px max|dDisp|=%.3g (gate <=1e-5), gpu-only=%d cpu-only=%d;"+
				" GPU vs float-serial: max|d|=%.3g px, set-mismatch=%d",
				tpl.count, matched, max_d_center, max_d_disp, gpu_only, cpu_only,
				max_d_float, hole_mismatch));
	}

	// ---- DP rung D4 (ladder ratified 07/16/2026): the whole per-scene pose solve
	// as ONE device entry. Scene 0 (per template/sequence) always runs the
	// host-driven loop - it seeds the resident per-sequence state (camera blocks,
	// task template, LMA centers) through the production C1/C2 register path -
	// and is then REPLAYED through the DP entry as the one-shot oracle: per-cycle
	// packed rows, the final anchor and the last-cycle peak rows (keyed by packed
	// index, the B3 rule) must all match BIT-EXACT. PASS -> scenes 1+ run one
	// tp_proc_exec_pose_scene_dp call each; FAIL -> DP disabled for the run
	// (fail-safe: a real divergence must never drive production).
	// By Claude on 07/17/2026.
	private static final int DP_PACKED = 25; // POSE_LMA_RESIDENT_RESULT: {valid,H[9],b[3],delta[3],candidate[3],rms4,accepted,stop}
	private static final int DP_MSTATS = 5;  // POSE_MEASURE_STATUS_INTS: {npk,pairs,surviving,misaligned,corr_rows}
	private static boolean dp_path_reported = false;   // once-per-program "path active" note
	private static boolean dp_overlap_reported = false; // once-per-program D5 "overlap active" note // By Claude on 07/17/2026
	private static boolean dp_oracle_failed = false;   // fail-safe: FAIL (or a mid-run entry failure) keeps the run host-driven
	private static Object  dp_seeded_key = null;       // the GpuTaskTemplate whose scene-0 legacy pass seeded + validated DP

	/** One DP scene entry's readbacks (the single end-of-scene D2H). */
	private static final class DpSceneResult {
		int      npk;    // last-cycle corr-row count (>0)
		float [] trace;  // [num_cycles*DP_PACKED] per-cycle packed rows
		int []   mstats; // [num_cycles*DP_MSTATS] per-cycle measure counts
		float [] result; // final packed [DP_PACKED]
		float [] vector; // final anchor [3]
		float [] prep;   // last full prepare [8]
		int []   stats;  // last-cycle {pairs, surviving, misaligned}
	}

	/**
	 * Run ONE pose_scene_dp entry for a scene: refresh the per-scene descriptor
	 * (the exact b3CycleSetup bytes), build the full-prepare objective rows (the
	 * exact IntersceneLma.residentPrepare floats: pull = the predicted/pull ATR,
	 * regweights = param_regweights at the 3-angle indices) and launch. Camera/
	 * task/centers groups follow the C2 register contract (resident unless new).
	 * Returns null on a native failure (message printed - the FAIL-safe hook).
	 * By Claude on 07/17/2026.
	 */
	private static DpSceneResult dpSceneEntry(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final PoseMeasureSetup ms,
			final GpuQuad       gpuQuad,
			final int           num_tiles,   // full-grid LMA tile count (pXpYD_center.length; corr indices carry full-grid tile numbers)
			final double [][]   entry_pose,  // {xyz, atr} at chain entry (the prediction)
			final double [][]   pull_pose,   // the pull pose (same prediction on the lean path)
			final double []     reg_weights, // ilp.ilma_regularization_weights (full-length)
			final double        lma_lambda,
			final int           num_cycles,
			final Runnable      overlap_work) { // D5: non-null = launch -> run this -> collect (conditions the NEXT scene on the copy stream) // By Claude on 07/17/2026
		final double disparity_corr = clt_parameters.imp.disparity_corr + center_CLT.getDispInfinityRef();
		b3CycleSetup(clt_parameters, image_dtt, scene, gpuQuad,
				ms.tpl.count, ms.tpl.skeleton[0][0].getSize(),
				disparity_corr, ms.minPx, ms.maxPx, ms.minPy, ms.maxPy,
				ms.taskCodeMain, ms.taskCodeSub,
				ms.dxm, ms.dym, ms.dxs, ms.dys, ms.gain, ms.gainSub);
		final float [] pull = new float [IntersceneLmaFloat.NUM_PARAMS];
		final float [] regw = new float [IntersceneLmaFloat.NUM_PARAMS];
		for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
			pull[par] = (float) pull_pose[1][par];
			regw[par] = (float) reg_weights[ErsCorrection.DP_DSAZ + par];
		}
		final DpSceneResult r = new DpSceneResult();
		r.trace  = new float [num_cycles * DP_PACKED];
		r.mstats = new int [num_cycles * DP_MSTATS];
		r.result = new float [DP_PACKED];
		r.vector = new float [IntersceneLmaFloat.NUM_PARAMS];
		r.prep   = new float [8]; // POSE_LMA_PREP_RESULT
		r.stats  = new int [3];
		if (overlap_work != null) {
			// D5 split (design ratified 07/17/2026): enqueue the whole chain, condition
			// the NEXT scene while it runs (all its GPU work on the copy stream), then
			// collect the same end-of-scene readbacks. Identical bytes to the blocking
			// entry (the scene_dp_split case). By Claude on 07/17/2026.
			r.npk = gpuQuad.execPoseSceneDpLaunch(
					ms.newTemplate ? ms.tpl.refCamera : null,
					ms.sceneCamera,                     // null = keep resident (same scene)
					IntersceneLmaFloat.toFloat3(ZERO3), // reference pose = zeros (virtual center)
					IntersceneLmaFloat.toFloat3(ZERO3),
					IntersceneLmaFloat.toFloat3(entry_pose[0]),
					IntersceneLmaFloat.toFloat3(entry_pose[1]),
					ms.newTemplate ? ms.tpl.map : null,
					ms.newTemplate ? ms.tpl.centers : null,
					num_cycles,
					(float) lma_lambda,
					0.0f,                               // pure_weight: full prepare reads it LIVE on-device
					(float) clt_parameters.ilp.ilma_rms_diff,
					num_tiles,
					0xff,                               // the sum slot (the consolidated average x center)
					GPUTileProcessor.CORR_NTILE_SHIFT,
					IntersceneLmaFloat.NUM_COMPONENTS,
					!clt_parameters.imp.eig_xy_lma,     // use_eigen (prepareLMA passes eigen=null only when eig_xy_lma)
					(float) clt_parameters.imp.eig_min_sqrt,
					(float) clt_parameters.imp.eig_max_sqrt,
					0.0f,                               // min_confidence (tiles pre-calibrated)
					false,                              // same_weights
					false,                              // prepare_light: production = per-cycle FULL prepare (pose_freeze_cycle==0)
					null,                               // LMA centers: resident (seeded by the scene-0 legacy pass)
					pull, regw,
					IntersceneLmaFloat.toFloat3(entry_pose[1]), // entry anchor = the 3-angle parameter vector
					true, true);                        // want_trace, want_mstats (both always read below)
			if (r.npk > 0) {
				if (!dp_overlap_reported) {
					dp_overlap_reported = true;
					System.out.println("CuasPoseRT: D5 overlap active (DP launch -> condition next scene "+
							"on the copy stream -> collect; conditioning+H2D hidden under the scene DP entry)");
				}
				overlap_work.run();
				r.npk = gpuQuad.execPoseSceneDpCollect(r.trace, r.mstats, r.result, r.vector, r.prep, r.stats);
			}
		} else {
		r.npk = gpuQuad.execPoseSceneDp(
				ms.newTemplate ? ms.tpl.refCamera : null,
				ms.sceneCamera,                     // null = keep resident (same scene)
				IntersceneLmaFloat.toFloat3(ZERO3), // reference pose = zeros (virtual center)
				IntersceneLmaFloat.toFloat3(ZERO3),
				IntersceneLmaFloat.toFloat3(entry_pose[0]),
				IntersceneLmaFloat.toFloat3(entry_pose[1]),
				ms.newTemplate ? ms.tpl.map : null,
				ms.newTemplate ? ms.tpl.centers : null,
				num_cycles,
				(float) lma_lambda,
				0.0f,                               // pure_weight: full prepare reads it LIVE on-device
				(float) clt_parameters.ilp.ilma_rms_diff,
				num_tiles,
				0xff,                               // the sum slot (the consolidated average x center)
				GPUTileProcessor.CORR_NTILE_SHIFT,
				IntersceneLmaFloat.NUM_COMPONENTS,
				!clt_parameters.imp.eig_xy_lma,     // use_eigen (prepareLMA passes eigen=null only when eig_xy_lma)
				(float) clt_parameters.imp.eig_min_sqrt,
				(float) clt_parameters.imp.eig_max_sqrt,
				0.0f,                               // min_confidence (tiles pre-calibrated)
				false,                              // same_weights
				false,                              // prepare_light: production = per-cycle FULL prepare (pose_freeze_cycle==0)
				null,                               // LMA centers: resident (seeded by the scene-0 legacy pass)
				pull, regw,
				IntersceneLmaFloat.toFloat3(entry_pose[1]), // entry anchor = the 3-angle parameter vector
				r.trace, r.mstats, r.result, r.vector, r.prep, r.stats);
		}
		if (r.npk <= 0) {
			System.out.println("CuasPoseRT: DP scene entry FAILED for scene "+scene.getImageName()+
					" rc="+r.npk+" ("+gpuQuad.poseLastError()+")");
			return null;
		}
		return r;
	}

	/**
	 * D4 production path: ONE DP entry replaces the whole measure<->solve loop,
	 * then the scene bookkeeping/reporting is reconstructed from the per-cycle
	 * trace by the EXACT leanFitScene/IntersceneLma double rules (all Java-side
	 * state is derived from the same widened GPU floats, so the printed records
	 * stay byte-identical to the host-driven loop):
	 *  - per-cycle: candidate = packed[16..18]; accepted = packed[23]; a rejected
	 *    step == the legacy runLma() -1 (scene failed, caller coasts);
	 *  - dATR = L2(candidate - cycle-entry vector) in double; inner iterations =
	 *    stop ? 0 : 1 (the validated 1-inner static shape);
	 *  - RMS: measured = packed[20], solved = packed[22]; the scene RMS = the
	 *    last cycle's candidate pair (all cycles must accept to get here);
	 *  - the last-cycle measurement (peaks) stays resident - fetched and unpacked
	 *    exactly like leanMeasure() for -POSE-RT-HYPER and the weight/num fields.
	 * @return fitted {xyz,atr}, or null with dp_fallback[0]=false = scene failed
	 *         (coast), or null with dp_fallback[0]=true = run the host-driven loop.
	 * By Claude on 07/17/2026.
	 */
	private static double [][] leanFitSceneDp(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double [][]   predicted,
			final double []     reg_weights,
			final int           margin,
			final double [][]   mb_vectors,
			final double        mb_tau,
			final double        mb_max_gain,
			final double []     lma_rms,
			final double [][][] coord_motion_rslt,
			final int []        ncyc_out,
			final boolean []    dp_fallback, // out: [0]=true -> caller runs the host-driven loop
			final Runnable      overlap_work, // D5: non-null = launch/collect split with next-scene conditioning between // By Claude on 07/17/2026
			final int           debugLevel) {
		final RtPoseProfile rtProfile = RT_POSE_PROFILE.get();
		final GpuQuad gpuQuad = center_CLT.getGPUQuad();
		final PoseMeasureSetup ms = poseMeasureSetup(center_CLT, scene, pXpYD_center,
				selection, mb_vectors, mb_tau, mb_max_gain, margin, gpuQuad);
		if ((ms == null) || (ms.tpl != dp_seeded_key)) {
			// degenerate MB this scene, or a NEW template/sequence (bootstrap second
			// pass, next sequence) - the host-driven loop re-seeds and re-validates
			dp_fallback[0] = true;
			return null;
		}
		final int K = clt_parameters.curt.pose_cycles;
		final double lma_lambda = (clt_parameters.curt.pose_lambda > 0) ?
				clt_parameters.curt.pose_lambda : clt_parameters.ilp.ilma_lambda;
		final long profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
		final DpSceneResult r = dpSceneEntry(clt_parameters, image_dtt, center_CLT, scene,
				ms, gpuQuad, pXpYD_center.length, predicted, predicted, reg_weights,
				lma_lambda, K, overlap_work);
		if (rtProfile != null) rtProfile.addElapsed(RtPoseProfile.DP_SCENE, profileStart);
		if (r == null) { // FAIL-safe: a mid-run native failure degrades to the host-driven loop
			dp_oracle_failed = true;
			System.out.println("CuasPoseRT: DP scene entry DISABLED for this run - host-driven loop takes over");
			dp_fallback[0] = true;
			return null;
		}
		if (ms.newScene) ms.tpl.uploadedScene = scene.getImageName();
		if (!dp_path_reported) {
			dp_path_reported = true;
			System.out.println("CuasPoseRT: DP scene entry active (ONE tp_proc_exec_pose_scene_dp "+
					"per scene: "+K+" x [measure chain -> full prepare -> resident LMA step -> commit] "+
					"self-chained on-device; per-scene D2H = per-cycle trace + final packed + last-cycle peaks)");
		}
		// consolidation warning parity (the B3 per-cycle print)
		for (int cyc = 0; cyc < K; cyc++) {
			final int mis = r.mstats[cyc * DP_MSTATS + 3];
			if (mis != 0) {
				System.out.println("CuasPoseRT B3 measure cycle: WARNING - "+mis+
						" misaligned MB pairs, "+r.mstats[cyc * DP_MSTATS + 1]+" pairs -> "+
						r.mstats[cyc * DP_MSTATS + 2]+" tiles");
			}
		}
		// per-cycle bookkeeping (the leanFitScene/IntersceneLma double rules)
		final double [] vec = predicted[1].clone();
		final double [] cycle_datr =       new double [K];
		final int []    cycle_inner =      new int [K];
		final double [] cycle_rms_meas =   new double [K];
		final double [] cycle_rms_solved = new double [K];
		double last_full_rms = Double.NaN, last_pure_rms = Double.NaN;
		int failed_cycle = -1;
		for (int cyc = 0; cyc < K; cyc++) {
			final int row = cyc * DP_PACKED;
			cycle_rms_meas[cyc] = r.trace[row + 20]; // re-measured pure RMS at this cycle's pose
			if (r.trace[row + 23] != 1.0f) { // rejected step == the legacy runLma() -1
				failed_cycle = cyc;
				break;
			}
			double d2 = 0.0;
			for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
				final double cand = r.trace[row + 16 + par]; // widened exactly, like the Java state machine
				final double d = cand - vec[par];
				d2 += d * d;
				vec[par] = cand;
			}
			cycle_datr[cyc] = Math.sqrt(d2);
			cycle_inner[cyc] = (r.trace[row + 24] == 1.0f) ? 0 : 1; // runLma() iter with the 1-inner cap
			cycle_rms_solved[cyc] = r.trace[row + 22]; // model-predicted pure RMS after the solve
			last_full_rms = r.trace[row + 21];
			last_pure_rms = r.trace[row + 22];
		}
		if (failed_cycle >= 0) {
			// The host-driven loop cannot be re-entered mid-scene (the interleave
			// already advanced) - report like its runLma() failure and coast.
			System.out.println("leanFitScene(): LMA failed, lmaResult=-1"+
					" (DP: GPU step rejected at cycle "+failed_cycle+" of "+K+")");
			return null; // dp_fallback stays false: scene failed, caller coasts the prediction
		}
		// QC diagnostic (fixed-cycle mode) - the exact leanFitScene line
		final double last_diff_atr = cycle_datr[K - 1];
		if (!(last_diff_atr < clt_parameters.imp.exit_change_atr) && (debugLevel > -4)) {
			System.out.println("CuasPoseRT QC: scene "+scene.getImageName()+" pose NOT settled after "+
					K+" fixed cycles: last ATR change "+last_diff_atr+
					" >= exit_change_atr "+clt_parameters.imp.exit_change_atr);
		}
		if (ncyc_out != null) ncyc_out[0] = K;
		if ((clt_parameters.curt.pose_lma_debug >= 1) && (debugLevel > -4)) { // the per-scene cycles trace line
			final StringBuilder tsb = new StringBuilder();
			for (int i = 0; i < K; i++) tsb.append(String.format(" %.3g", cycle_datr[i]));
			final StringBuilder isb = new StringBuilder();
			for (int i = 0; i < K; i++) isb.append(" "+cycle_inner[i]);
			final StringBuilder rsb = new StringBuilder();
			for (int i = 0; i < K; i++) rsb.append(String.format(" %.4f>%.4f",
					cycle_rms_meas[i], cycle_rms_solved[i]));
			System.out.println("CuasPoseRT cycles: scene "+scene.getImageName()+
					" ("+K+" of max "+K+", lambda0 "+lma_lambda+") dATR/cycle ="+tsb+
					"; innerLMA ="+isb+"; RMS meas>solved ="+rsb);
		}
		// last-cycle measurement: peaks/corr indices stayed resident - fetch and
		// unpack EXACTLY like leanMeasure() (the -POSE-RT-HYPER + weight/num source)
		final int [] corr_indices = gpuQuad.getCorrIndices();
		final float [] gpu_peaks = (corr_indices != null) ?
				gpuQuad.fetchCorr2DPeaks(corr_indices.length) : null;
		if (gpu_peaks == null) {
			dp_oracle_failed = true;
			System.out.println("CuasPoseRT: DP scene entry peak fetch FAILED for scene "+
					scene.getImageName()+" - DP DISABLED, host-driven loop takes over");
			dp_fallback[0] = true;
			return null;
		}
		final int num_pix = pXpYD_center.length;
		final double [][] centers =    new double [num_pix][];
		final double [][] vector_XYS = new double [num_pix][];
		final double [][] eigen =      new double [num_pix][];
		int num_meas = 0;
		for (int n = 0; n < corr_indices.length; n++) {
			if ((corr_indices[n] & ((1 << GPUTileProcessor.CORR_NTILE_SHIFT) - 1)) != 0xff) continue;
			final int i = corr_indices[n] >> GPUTileProcessor.CORR_NTILE_SHIFT;
			if ((i < 0) || (i >= num_pix) || (pXpYD_center[i] == null)) continue;
			final int row = n * GpuQuad.PEAK_ROW_FLOATS;
			if (gpu_peaks[row + GpuQuad.PEAK_ROW_FLOATS - 1] == 0.0f) continue; // rejected
			centers[i] =    pXpYD_center[i];
			vector_XYS[i] = new double [] {gpu_peaks[row], gpu_peaks[row + 1], gpu_peaks[row + 2]};
			eigen[i] =      new double [] {gpu_peaks[row + 3], gpu_peaks[row + 4],
					gpu_peaks[row + 5], gpu_peaks[row + 6]};
			num_meas++;
		}
		if (num_meas == 0) return null; // the leanMeasure() failure semantics (coast)
		if (lma_rms != null) {
			lma_rms[0] = last_full_rms;
			if (lma_rms.length > 1) lma_rms[1] = last_pure_rms;
			double sw = 0; int nm = 0;
			for (double [] v : vector_XYS) if (v != null) { sw += v[2]; nm++; }
			if (lma_rms.length > 2) lma_rms[2] = sw;
			if (lma_rms.length > 3) lma_rms[3] = nm;
		}
		if (coord_motion_rslt != null) {
			final double [][][] cm = {centers, vector_XYS, eigen};
			for (int i = 0; (i < coord_motion_rslt.length) && (i < cm.length); i++) {
				coord_motion_rslt[i] = cm[i];
			}
		}
		if (debugLevel > -3) { // the leanFitScene exit line (same widened-float double)
			System.out.println("Adjusted interscene, iteration="+K+", last RMS = "+
					last_full_rms+" (lean)");
		}
		return new double [][] {predicted[0].clone(), vec};
	}

	/**
	 * D4 one-shot first-scene oracle: the host-driven loop just fitted this scene
	 * (its per-cycle resident-step results are in {@code capture}); replay the
	 * WHOLE scene through the DP entry from the same entry pose and compare
	 * bit-exact - per-cycle candidate/RMS/decision rows, the final anchor, and
	 * the last-cycle peak rows keyed by packed index (correlate2D_inter row order
	 * is legitimately nondeterministic - the B3 lesson). PASS arms the DP path
	 * for this template; FAIL (or a native failure) disables DP for the run.
	 * By Claude on 07/17/2026.
	 */
	private static void dpFirstSceneOracle(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double [][]   predicted,
			final double []     reg_weights,
			final int           margin,
			final double [][]   mb_vectors,
			final double        mb_tau,
			final double        mb_max_gain,
			final double        lma_lambda,
			final ArrayList<IntersceneLmaFloat.ResidentLmaStepResult> capture,
			final double []     final_atr, // the host-driven loop's fitted angles
			final GpuQuad       gpuQuad) {
		final int K = clt_parameters.curt.pose_cycles;
		if (capture.size() != K) {
			// A SUCCESSFUL scene without K resident-step results = the resident LMA
			// machinery is inactive (persistent config, not a transient) - disable.
			// (A failed scene never reaches the oracle and retries naturally.)
			dp_oracle_failed = true;
			System.out.println("CuasPoseRT D4 oracle: captured "+capture.size()+
					" resident steps (need "+K+") - resident LMA machinery inactive, "+
					"DP scene entry DISABLED for this run");
			return;
		}
		// the host-driven loop's last-cycle rows, BEFORE the replay overwrites them
		final int [] legacy_idx = gpuQuad.getCorrIndices();
		final float [] legacy_peaks = (legacy_idx != null) ?
				gpuQuad.fetchCorr2DPeaks(legacy_idx.length) : null;
		if (legacy_peaks == null) {
			System.out.println("CuasPoseRT D4 oracle: legacy peak fetch failed - retrying on the next scene");
			return;
		}
		final PoseMeasureSetup ms = poseMeasureSetup(center_CLT, scene, pXpYD_center,
				selection, mb_vectors, mb_tau, mb_max_gain, margin, gpuQuad);
		if (ms == null) {
			System.out.println("CuasPoseRT D4 oracle: measure setup degenerate - retrying on the next scene");
			return;
		}
		final DpSceneResult r = dpSceneEntry(clt_parameters, image_dtt, center_CLT, scene,
				ms, gpuQuad, pXpYD_center.length, predicted, predicted, reg_weights,
				lma_lambda, K, null); // oracle replay always takes the blocking entry (D5 overlap starts on production scenes) // By Claude on 07/17/2026
		if (r == null) {
			dp_oracle_failed = true;
			System.out.println("CuasPoseRT: DP scene entry DISABLED for this run (oracle replay failed) - staying host-driven");
			return;
		}
		// per-cycle packed rows vs the captured resident-step results (bit compare)
		long step_mism = 0;
		for (int cyc = 0; cyc < K; cyc++) {
			final IntersceneLmaFloat.ResidentLmaStepResult s = capture.get(cyc);
			final int row = cyc * DP_PACKED;
			if ((s.step == null) || (s.step.candidate == null) ||
					(s.step.candidate.length != IntersceneLmaFloat.NUM_PARAMS)) { step_mism++; continue; }
			for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
				if (Float.floatToRawIntBits(r.trace[row + 16 + par]) !=
						Float.floatToRawIntBits(s.step.candidate[par])) step_mism++;
			}
			if (Float.floatToRawIntBits(r.trace[row + 19]) != Float.floatToRawIntBits(s.currentRms))       step_mism++;
			if (Float.floatToRawIntBits(r.trace[row + 20]) != Float.floatToRawIntBits(s.currentPureRms))   step_mism++;
			if (Float.floatToRawIntBits(r.trace[row + 21]) != Float.floatToRawIntBits(s.candidateRms))     step_mism++;
			if (Float.floatToRawIntBits(r.trace[row + 22]) != Float.floatToRawIntBits(s.candidatePureRms)) step_mism++;
			if ((r.trace[row + 23] == 1.0f) != s.accepted) step_mism++;
			if ((r.trace[row + 24] == 1.0f) != s.stop)     step_mism++;
		}
		// final anchor vs the host-driven fitted angles (widen/narrow is exact)
		int anchor_mism = 0;
		for (int par = 0; par < IntersceneLmaFloat.NUM_PARAMS; par++) {
			if (Float.floatToRawIntBits(r.vector[par]) !=
					Float.floatToRawIntBits((float) final_atr[par])) anchor_mism++;
		}
		// last-cycle rows, order-independent by packed index (the B3 rule)
		final int [] dp_idx = gpuQuad.getCorrIndices();
		final float [] dp_peaks = (dp_idx != null) ? gpuQuad.fetchCorr2DPeaks(dp_idx.length) : null;
		int missing = 0, extra = 0;
		long row_mism = 0;
		if (dp_peaks == null) {
			missing = legacy_idx.length; // fetch failure counts as a full mismatch
		} else {
			final java.util.HashMap<Integer, Integer> legacy_rows = new java.util.HashMap<>();
			for (int n = 0; n < legacy_idx.length; n++) legacy_rows.put(legacy_idx[n], n);
			final int row_floats = GpuQuad.PEAK_ROW_FLOATS;
			for (int n = 0; n < dp_idx.length; n++) {
				final Integer on = legacy_rows.remove(dp_idx[n]);
				if (on == null) { extra++; continue; }
				for (int k = 0; k < row_floats; k++) {
					if (Float.floatToRawIntBits(dp_peaks[n * row_floats + k]) !=
							Float.floatToRawIntBits(legacy_peaks[on * row_floats + k])) row_mism++;
				}
			}
			missing = legacy_rows.size();
		}
		final boolean pass = (step_mism == 0) && (anchor_mism == 0) &&
				(missing == 0) && (extra == 0) && (row_mism == 0);
		System.out.println(String.format(
				"CuasPoseRT D4 DP-vs-host oracle (one-shot, scene %s, %d cycles):"+
				" step-row bit-mismatches=%d, final-anchor mismatches=%d;"+
				" last-cycle peaks (order-independent by packed index): %d rows,"+
				" unmatched host=%d dp=%d, bit-mismatches=%d -> %s",
				scene.getImageName(), K, step_mism, anchor_mism,
				legacy_idx.length, missing, extra, row_mism, pass ? "PASS" : "FAIL"));
		if (pass) {
			dp_seeded_key = ms.tpl; // scenes on this template now take the ONE-entry DP path
		} else { // fail-safe: a REAL divergence must never drive production
			dp_oracle_failed = true;
			System.out.println("CuasPoseRT: DP scene entry DISABLED for this run (oracle FAIL) - staying host-driven");
		}
	}

	private static int GPUTileProcessorDttSize() {
		return com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE;
	}

	/**
	 * Precomputed envelope de-bias array for the lean peak measurement (rung-2 optional
	 * hook, Andrey's ruling 07/13/2026 - default OFF). The pixel-domain correlation of two
	 * MCLT-windowed tiles is modulated by the window AUTOCORRELATION envelope (separable,
	 * peaked at center): a displaced peak rides an envelope that decays away from center,
	 * so its centroid is pulled back toward zero - the CM argmax under-reports offsets by
	 * ~0.75-0.9x (probed 07/13; the estimator itself is unbiased, the envelope is the bias).
	 * De-bias = multiply the correlation by envelope^-frac before peak extraction.
	 * The GPU kernel takes the SAME array as an optional pointer (NULL = off); this is the
	 * single source of the array for the Java oracle, the export case, and the JNA host.
	 * @param transform_size DTT size (8): window = sin(pi/(2*ts)*(i+0.5)), 2*ts samples
	 * @param frac exponent fraction: 0 = all-ones (no-op), 1 = full envelope divide
	 * @return [(2*ts-1)^2] multiplicative de-bias, 1.0 at the center pixel
	 * By Claude on 07/13/2026.
	 */
	public static double [] windowAutocorrDebias(final int transform_size, final double frac) {
		final int wnd_len = 2 * transform_size;             // MCLT window length (16)
		final int corr_width = 2 * transform_size - 1;      // correlation tile side (15)
		final double [] wnd = new double [wnd_len];
		for (int i = 0; i < wnd_len; i++) {
			wnd[i] = Math.sin(Math.PI * (i + 0.5) / wnd_len);
		}
		final double [] acorr = new double [corr_width];    // 1D autocorr, index d+ (ts-1), d = -(ts-1)..(ts-1)
		for (int d = -(transform_size - 1); d <= (transform_size - 1); d++) {
			double s = 0.0;
			for (int i = Math.max(0, -d); i < Math.min(wnd_len, wnd_len - d); i++) {
				s += wnd[i] * wnd[i + d];
			}
			acorr[d + transform_size - 1] = s;
		}
		final double center = acorr[transform_size - 1];    // normalize so the center is 1.0
		final double [] debias = new double [corr_width * corr_width];
		for (int iy = 0; iy < corr_width; iy++) {
			for (int ix = 0; ix < corr_width; ix++) {
				final double env = (acorr[iy] / center) * (acorr[ix] / center); // separable 2D envelope
				debias[iy * corr_width + ix] = Math.pow(env, -frac);
			}
		}
		return debias;
	}

	/**
	 * Deterministic task-stream order (3-A3, GPU static shape): the setInterTasks*
	 * builders fill their output from worker threads (aTiles.getAndIncrement()), so the
	 * tile ORDER varies run-to-run and scene-to-scene even for identical selections.
	 * Sort ascending by (tileY, tileX): with the interior-shrunk fixed selection the
	 * pose chain then produces the SAME task stream every scene/iteration - the fixed
	 * per-sequence indices contract the GPU consolidation list and the pose_corr
	 * checkpoint test rely on. Correlation output stays index-addressed (packed
	 * tile+pair), so Java-side consumers are unaffected. By Claude on 07/13/2026.
	 */
	private static void sortTasksByTile(final TpTask [] tasks) {
		if (tasks == null) return;
		Arrays.sort(tasks, (a,b) -> (a.getTileY() != b.getTileY()) ?
				Integer.compare(a.getTileY(), b.getTileY()) :
				Integer.compare(a.getTileX(), b.getTileX()));
	}

	/**
	 * Lean v2 CUAS motion blur (Andrey's ruling 07/12/2026): ONE uniform blur vector for
	 * the whole scene, shared by all tiles. Under constant-rpm rotation the image flow is
	 * uniform across the frame up to the roll flow component (roll rate << azimuth/tilt
	 * rates) and the wobble contribution (second-degree) - both ignored by ruling.
	 * Evaluates the SAME Jacobian-times-rates product as the per-tile
	 * OpticalFlow.getMotionBlur at ONE representative point - the DEFINED reference tile
	 * nearest the frame center (a real tile provably valid in the per-tile path; a
	 * synthetic frame-center point with averaged disparity silently failed getMotionBlur's
	 * gates in the 07/12 run) - identical sign/scale conventions to the per-tile oracle -
	 * then broadcasts. Rates source unchanged (finite differences of predicted poses).
	 * The GPU migration consumes this as two scalars per scene: the MB TpTask pair
	 * differs from the main task set by a single shared pixel shift.
	 * By Claude on 07/12/2026, from Andrey's design.
	 * 3-B rung B2 (07/16/2026): the one-point evaluation now goes through the serial
	 * OpticalFlow.getMotionBlurPoint (identical double math, no IntersceneLma/thread
	 * scaffolding - the whole ~5.5 ms/scene B0-attributed MB-setup overhead); the
	 * legacy getMotionBlur stays as the pose_lma_debug one-shot oracle (caller) and
	 * as the per-tile fallback on degenerate return. By Claude on 07/16/2026.
	 * @return [2][pXpYD_center.length] uniform blur vectors (px/s), or null when the
	 *         representative point is degenerate (caller falls back to the per-tile path).
	 */
	public static double [][] uniformMotionBlur(
			final QuadCLT     center_CLT,
			final QuadCLT     scene,
			final double [][] pXpYD_center,
			final double []   camera_xyz,
			final double []   camera_atr,
			final double []   camera_xyz_dt,
			final double []   camera_atr_dt,
			final int         debugLevel) {
		final double [] best = mbRepresentativePoint(center_CLT, pXpYD_center);
		if (best == null) {
			System.out.println("uniformMotionBlur(): no defined in-gate reference tile at all ("+
					pXpYD_center.length+" tiles) - degenerate");
			return null;
		}
		final double [] mb1 = OpticalFlow.getMotionBlurPoint(
				center_CLT,     // QuadCLT     ref_scene
				scene,          // QuadCLT     scene
				best,           // double []   ref_pXpYD - the single representative point
				camera_xyz,     // double []   camera_xyz
				camera_atr,     // double []   camera_atr
				camera_xyz_dt,  // double []   camera_xyz_dt
				camera_atr_dt,  // double []   camera_atr_dt
				debugLevel);    // int         debug_level
		if (mb1 == null) {
			// self-explaining fallback: the point PASSED the mirrored gates, so the null
			// came from the projection math (getDPxSceneDParameters). Report everything
			// that was tried. By Claude on 07/12/2026.
			final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
			final double rr = Math.hypot(best[0] - 0.5 * wh[0], best[1] - 0.5 * wh[1]);
			System.out.println(String.format(
					"uniformMotionBlur(): degenerate at representative point pXpYD=(%.2f, %.2f, %.4f)"+
					" (r=%.1f px from frame center, passed gates), atr_dt=(%.3g, %.3g, %.3g),"+
					" xyz_dt=(%.3g, %.3g, %.3g) - silent failure inside getMotionBlurPoint",
					best[0], best[1], best[2], rr,
					camera_atr_dt[0], camera_atr_dt[1], camera_atr_dt[2],
					camera_xyz_dt[0], camera_xyz_dt[1], camera_xyz_dt[2]));
			return null;
		}
		final double [][] mb_vectors = new double [2][pXpYD_center.length];
		Arrays.fill(mb_vectors[0], mb1[0]);
		Arrays.fill(mb_vectors[1], mb1[1]);
		return mb_vectors;
	}

	/**
	 * The uniform-MB representative point: the DEFINED tile nearest the frame center
	 * that passes getMotionBlur's own gates (in-frame; disparity within its local
	 * min_disparity=-0.5 .. max_disparity=100.0) - mirrored here because a
	 * gate-rejected tile fails SILENTLY (NaN result). Extracted from
	 * uniformMotionBlur so the B2 one-shot oracle evaluates the legacy
	 * getMotionBlur at the SAME point. By Claude on 07/16/2026 (scan from 07/12).
	 * @return the winning pXpYD row (by reference), or null if none qualifies
	 */
	private static double [] mbRepresentativePoint(
			final QuadCLT     center_CLT,
			final double [][] pXpYD_center) {
		final int [] wh = center_CLT.getGeometryCorrection().getSensorWH();
		final double cx = 0.5 * wh[0], cy = 0.5 * wh[1];
		double best_r2 = Double.MAX_VALUE;
		double [] best = null;
		for (double [] p : pXpYD_center) {
			if ((p != null) &&
					!Double.isNaN(p[0]) && (p[0] >= 0) && (p[0] < wh[0]) &&
					!Double.isNaN(p[1]) && (p[1] >= 0) && (p[1] < wh[1]) &&
					!Double.isNaN(p[2]) && (p[2] >= -0.5) && (p[2] < 100.0)) {
				final double r2 = (p[0]-cx)*(p[0]-cx) + (p[1]-cy)*(p[1]-cy);
				if (r2 < best_r2) {
					best_r2 = r2;
					best = p;
				}
			}
		}
		return best;
	}

	/**
	 * Phase B lean per-scene fit: the measure<->solve cycle with leanMeasure() as the
	 * measurement engine and the same IntersceneLma solver/exit rules as the oracle
	 * (adjustPairsLMAInterscene). NOTE v1: NO motion-blur compensation in the lean path
	 * yet - expect the known v*tau bias vs stored (compare against the NOMB baseline,
	 * not MBEN). By Claude on 07/05/2026.
	 * @return fitted {xyz,atr} or null (measurement/LMA failure); fills lma_rms
	 *         ([0] rms, [2] sum weight, [3] tiles) and coord_motion_rslt (for -POSE-RT-HYPER)
	 */
	public static double [][] leanFitScene(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double []     center_disparity,
			final double [][]   pXpYD_center,
			final boolean []    selection,
			final double [][]   predicted,      // initial + pull
			final boolean []    param_select,
			final double []     reg_weights,
			final int           margin,
			final double [][]   mb_vectors,     // null - no MB compensation // By Claude on 07/05/2026
			final double        mb_tau,         // By Claude on 07/05/2026
			final double        mb_max_gain,    // By Claude on 07/05/2026
			final double []     lma_rms,        // out
			final double [][][] coord_motion_rslt, // out [3][][]
			final double [][][] corr_pd_out,    // null or len-1: last-cycle PD correlations // By Claude on 07/04/2026
			final float [][]    img_out,        // null or len-1: last-cycle composite render // By Claude on 07/04/2026
			final int []        ncyc_out,       // null or [1]: outer cycles executed (for the one-line scene report) // By Claude on 07/13/2026
			final Runnable      dp_overlap_work,// D5: non-null = condition the NEXT scene between the DP launch and collect // By Claude on 07/17/2026
			final int           debugLevel) {
		final RtPoseProfile rtProfile = RT_POSE_PROFILE.get();
		// DP rung D4 (ladder ratified 07/16/2026): eligibility for the ONE-entry-per-
		// scene path. The frozen lean shape only: fixed cycles, 1-inner cap, per-cycle
		// full prepare (freeze 0), uniform MB, JNA backend, no armed capture and no
		// debug-save holders (their fetches read mid-loop state). By Claude on 07/17/2026.
		final GpuQuad lmaGpu = center_CLT.getGPUQuad();
		final boolean dp_config = clt_parameters.curt.pose_scene_dp && !dp_oracle_failed &&
				(clt_parameters.curt.pose_cycles > 0) &&
				(clt_parameters.curt.pose_max_lma == 1) &&
				(clt_parameters.curt.pose_freeze_cycle == 0) &&
				clt_parameters.imp.lma_use_R && // the frozen 3-angle shape (2-angle = no resident machinery)
				clt_parameters.curt.pose_mb_uniform && (mb_vectors != null) &&
				lmaGpu.supportsPoseSceneDp() && lmaGpu.supportsResidentTaskSlots() &&
				!PoseCorrExport.isArmed() &&
				(corr_pd_out == null) && (img_out == null);
		if (dp_config && (dp_seeded_key != null) && (dp_seeded_key == gpu_task_template)) {
			// production DP branch: the oracle passed on this template's scene 0
			final boolean [] dp_fallback = {false};
			final double [][] dp_pose = leanFitSceneDp(clt_parameters, image_dtt, center_CLT,
					scene, pXpYD_center, selection, predicted, reg_weights, margin,
					mb_vectors, mb_tau, mb_max_gain, lma_rms, coord_motion_rslt, ncyc_out,
					dp_fallback, dp_overlap_work, debugLevel);
			if (!dp_fallback[0]) return dp_pose; // fitted, or null = scene failed (caller coasts)
			// fall through: the host-driven loop re-seeds/re-validates or finishes the run
		}
		// scene-0 (per template) oracle arm: capture every resident-step result of the
		// host-driven loop for the end-of-scene DP replay compare. By Claude on 07/17/2026.
		final ArrayList<IntersceneLmaFloat.ResidentLmaStepResult> dp_capture =
				(dp_config && (dp_seeded_key != gpu_task_template)) ?
						new ArrayList<IntersceneLmaFloat.ResidentLmaStepResult>() : null;
		final IntersceneLma intersceneLma = new IntersceneLma(
				clt_parameters.ilp.ilma_thread_invariant,
				0.0); // no disparity weight (2D only)
		intersceneLma.setPoseFxProvider((
				reference, sceneCamera,
				referenceXyz, referenceAtr, sceneXyz, sceneAtr,
				centers, poseSelection, calculateJacobian) -> {
			final IntersceneLmaFloat.Result result = lmaGpu.execPoseFxJacobian(
					reference, sceneCamera,
					referenceXyz, referenceAtr, sceneXyz, sceneAtr,
					centers, poseSelection, calculateJacobian);
			if ((result != null) && !pose_fx_path_reported) {
				pose_fx_path_reported = true;
				System.out.println("CuasPoseRT: CUDA pose getFxDerivs active "+
						"(float raw fx/J with per-call H2D/D2H; Java LMA policy retained)");
			}
			return result;
		}); // first production gate: replace only getFxDerivs per-tile work; callers remain unchanged // By Codex on 07/15/2026
		intersceneLma.setPoseLmaResidentStepProvider((
				lambda, weights, y, eigen, vector, pureWeight, rmsDiff, numTiles, capturePrepared) -> {
			final IntersceneLmaFloat.ResidentLmaStepResult result =
					lmaGpu.execPoseLmaResidentStep(
							lambda, weights, y, eigen, vector, pureWeight, rmsDiff,
							numTiles, capturePrepared);
			if ((dp_capture != null) && (result != null)) dp_capture.add(result); // D4 oracle arm // By Claude on 07/17/2026
			if ((result != null) && !pose_lma_step_path_reported) {
				pose_lma_step_path_reported = true;
				// Rung B (design 2026-07-16): GPU decision promoted to authoritative. By Claude on 07/16/2026.
				System.out.println("CuasPoseRT: resident CUDA float LMA decision AUTHORITATIVE "+
						"(GPU accept/stop/RMS drive the state machine; device-side set-index commit; "+
						"reject retains GPU state; Java one-shot oracle at pose_lma_debug>=1)");
			}
			return result;
		}); // production candidate: resident raw fx/J -> parallel prepare/reduce -> fixed 3x3 tail // By Codex on 07/15/2026
		// Rung C1 (design 2026-07-16): resident prepareLMA - assemble/normalize on the
		// GPU from the resident peak buffers; per-cycle H2D = camera state + pose
		// vector + centers + policy scalars; D2H = the 8-float compact result. The
		// packed-index slot/shift are CUAS host policy. By Claude on 07/17/2026.
		intersceneLma.setPoseFreezeCycles(clt_parameters.curt.pose_freeze_cycle); // C3b runtime policy // By Claude on 07/16/2026
		intersceneLma.setPoseLmaPrepareProvider((refCam, sceneCam, refXyz, refAtr,
				sceneXyz, sceneAtr, centers, numTiles, useEigen, eigMinSqrt, eigMaxSqrt,
				minConfidence, sameWeights, pullTargets, paramRegweights, light, capture) -> {
			final IntersceneLmaFloat.PrepareResidentResult result =
					lmaGpu.execPoseLmaPrepareResident(
							refCam, sceneCam, refXyz, refAtr, sceneXyz, sceneAtr, centers,
							numTiles, // rung C2: null camera/centers = keep resident; C3: light = frozen conditioning // By Claude on 07/17/2026
							0xff, GPUTileProcessor.CORR_NTILE_SHIFT,
							useEigen, eigMinSqrt, eigMaxSqrt, minConfidence, sameWeights,
							pullTargets, paramRegweights, light, capture);
			if ((result != null) && !pose_prepare_path_reported) {
				pose_prepare_path_reported = true;
				System.out.println("CuasPoseRT: resident CUDA prepareLMA active "+
						"(assemble+normalize on-device from resident peaks; steps run with "+
						"no per-step H2D; initial RMS from the first step)");
			}
			return result;
		});
		// Rung C0 (design 2026-07-16): route IntersceneLma sub-stage samples into the
		// RT profiler - attributes prepareLMA's budget and the cross-cutting fx
		// boundaries (JNA roundtrip, setupERS) before C1 goes resident. Null profiler
		// (non-RT callers) = zero overhead. By Claude on 07/17/2026.
		intersceneLma.setProfileSink((stage, ns) -> {
			final RtPoseProfile prof = RT_POSE_PROFILE.get();
			if (prof == null) return;
			switch (stage) {
			case IntersceneLma.PROF_PREP_SETUP:  prof.add(RtPoseProfile.PREP_SETUP,  ns); break;
			case IntersceneLma.PROF_PREP_FX1:    prof.add(RtPoseProfile.PREP_FX1,    ns); break;
			case IntersceneLma.PROF_PREP_WJTJ:   prof.add(RtPoseProfile.PREP_WJTJ,   ns); break;
			case IntersceneLma.PROF_PREP_FX2:    prof.add(RtPoseProfile.PREP_FX2,    ns); break;
			case IntersceneLma.PROF_FX_JNA:      prof.add(RtPoseProfile.FX_JNA,      ns); break;
			case IntersceneLma.PROF_FX_SETUPERS: prof.add(RtPoseProfile.FX_SETUPERS, ns); break;
			}
		});
		intersceneLma.setNormalEquationProvider((weights, jt, ymfxWeighted) -> {
			final long profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			final double [] products = lmaGpu.execLmaNormalProducts(weights, jt, ymfxWeighted);
			if (rtProfile != null) rtProfile.addLmaGpuProducts(profileStart);
			if ((products != null) && !lma_products_path_reported) {
				lma_products_path_reported = true;
				System.out.println("CuasPoseRT: GPU LMA normal-equation products active "+
						"(lma_normal_products, double debug/fallback oracle)");
			}
			return products;
		}); // Java-double debug/fallback oracle; resident float is the lean production candidate // By Codex on 07/15/2026
		double [][] scene_xyzatr0 = new double [][] {predicted[0].clone(), predicted[1].clone()};
		double [][][] cm = null;
		// lean v2 (Andrey's ruling 07/12/2026): pose_cycles > 0 = run EXACTLY that many
		// measure<->solve cycles, no convergence-based early exit (CUAS scenes are
		// near-identical - the needed count is a constant of the setup; a fixed count
		// gives the GPU pipeline a static launch shape). The convergence delta is still
		// computed below as a QC diagnostic. 0 = legacy convergence exit (flying-camera/
		// varying-scene applications). By Claude on 07/12/2026.
		final int fixed_cycles = clt_parameters.curt.pose_cycles;
		final int max_cycles = (fixed_cycles > 0) ? fixed_cycles : clt_parameters.imp.max_cycles;
		double last_diff_atr = Double.NaN; // By Claude on 07/12/2026
		final double [] cycle_datr = new double [max_cycles]; // per-cycle ATR-change trace (convergence trajectory) // By Claude on 07/12/2026
		final int [] cycle_inner = new int [max_cycles]; // inner runLma iterations per outer cycle // By Claude on 07/12/2026
		// Chain-rule diagnostic (Andrey 07/12/2026): per cycle, the RE-MEASURED pure RMS
		// (after re-correlation, from prepareLMA) vs the MODEL-PREDICTED pure RMS after
		// the solve (from runLma). Jacobian/chain-rule error signature: solved_n low but
		// meas_{n+1} bounces back up (the model overpromised, re-correlation "takes it
		// back"). Healthy damping signature: meas_{n+1} ~ solved_n, both creep down.
		final double [] cycle_rms_meas   = new double [max_cycles]; // By Claude on 07/12/2026
		final double [] cycle_rms_solved = new double [max_cycles]; // By Claude on 07/12/2026
		int ncyc = 0; // cycles actually executed // By Claude on 07/12/2026
		// lean v2 (Andrey 07/12/2026): the inner LMA restarts at ilp.ilma_lambda (0.1) every
		// outer cycle; combined with the 0.1%-improvement inner exit this under-relaxes the
		// outer loop (~0.8/cycle decay, 6-13 cycles observed). pose_lambda > 0 overrides
		// with a near-Gauss-Newton start for the near-linear 3-angle problem.
		final double lma_lambda = (clt_parameters.curt.pose_lambda > 0) ?
				clt_parameters.curt.pose_lambda : clt_parameters.ilp.ilma_lambda; // By Claude on 07/12/2026
		int nlma = 0;
		for (; nlma < max_cycles; nlma++) {
			// Rung A (design 2026-07-16): the debug holders are documented "last cycle"
			// products - with fixed cycles pass them ONLY on the last cycle (3 of 4 debug
			// fetches per scene were pure overwrite waste = the bulk of the 43% "leanMeasure
			// other" in the 00:25:48 profile). Legacy convergence mode (fixed_cycles==0)
			// keeps every-cycle passing so an early exit still leaves the holders filled.
			// By Claude on 07/16/2026.
			final boolean debug_cycle = (fixed_cycles <= 0) || (nlma == (max_cycles - 1));
			cm = leanMeasure(
					clt_parameters, image_dtt, center_CLT, scene,
					center_disparity, pXpYD_center, selection,
					scene_xyzatr0[0], scene_xyzatr0[1], margin,
					mb_vectors, mb_tau, mb_max_gain,
					debug_cycle ? corr_pd_out : null,
					debug_cycle ? img_out : null,
					debugLevel);
			if (cm == null) {
				PoseCorrExport.sceneFailed(scene); // drop a partial pose_corr capture (armed only) // By Claude on 07/13/2026
				return null;
			}
			final double [][] eigen_masked = clt_parameters.imp.eig_xy_lma ? null : cm[2];
			long profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			intersceneLma.prepareLMA(
					scene_xyzatr0,                    // scene_xyzatr0
					predicted,                        // scene_xyz_pull
					new double [2][3],                // ref_xyzatr
					scene,                            // scene_QuadClt
					center_CLT,                       // reference_QuadClt
					param_select,                     // param_select
					reg_weights,                      // param_regweights
					clt_parameters.imp.eig_max_sqrt,  // eig_max_sqrt (line = infinite feature)
					clt_parameters.imp.eig_min_sqrt,  // eig_min_sqrt
					eigen_masked,                     // eigen
					cm[1],                            // vector_XYS
					0.0,                              // min_confidence (tiles pre-calibrated)
					null,                             // reliable_ref (already selected)
					cm[0],                            // centers
					false,                            // same_weights
					(nlma == 0),                      // first_run
					null,                             // dbg_prefix
					// pose_lma_debug boost so the one-shot PREPARE oracle arms like the
					// step oracle does (was imp.debug_level only - the C1 gate run went
					// resident from cycle 0 without printing the bit-exact compare).
					// By Claude on 07/17/2026.
					Math.max(clt_parameters.imp.debug_level, clt_parameters.curt.pose_lma_debug));  // debug_level
			if (rtProfile != null) rtProfile.addElapsed(RtPoseProfile.LMA_PREPARE, profileStart);
			if ((clt_parameters.curt.pose_lma_debug >= 1) && !float_jacobian_oracle_reported) {
				float_jacobian_oracle_reported = true;
				final boolean [] float_selection = new boolean [cm[0].length];
				for (int tile = 0; tile < float_selection.length; tile++) {
					float_selection[tile] = (cm[0][tile] != null) && (cm[1][tile] != null) &&
							(cm[1][tile].length > 2) && (cm[1][tile][2] > 0.0);
				}
				final ErsCorrection reference_ers = center_CLT.getErsCorrection();
				final ErsCorrection scene_ers = scene.getErsCorrection();
				final IntersceneLmaFloat.Camera reference_camera = IntersceneLmaFloat.Camera.capture(reference_ers);
				final IntersceneLmaFloat.Camera scene_camera = IntersceneLmaFloat.Camera.capture(scene_ers);
				final float [] reference_xyz = IntersceneLmaFloat.toFloat3(ZERO3);
				final float [] reference_atr = IntersceneLmaFloat.toFloat3(ZERO3);
				final float [] scene_xyz = IntersceneLmaFloat.toFloat3(scene_xyzatr0[0]);
				final float [] scene_atr = IntersceneLmaFloat.toFloat3(scene_xyzatr0[1]);
				final float [] flat_centers = IntersceneLmaFloat.flattenCenters(cm[0]);
				final IntersceneLmaFloat.Result float_result = IntersceneLmaFloat.getFxDerivs(
						reference_camera, scene_camera,
						reference_xyz, reference_atr, scene_xyz, scene_atr,
						flat_centers,
						float_selection,
						null); // validate raw pixel/J first; eigen transform is a separate layer
				final IntersceneLmaFloat.Comparison float_comparison = IntersceneLmaFloat.compareToDouble(
						float_result, reference_ers, scene_ers,
						ZERO3, ZERO3, scene_xyzatr0[0], scene_xyzatr0[1], cm[0],
						float_selection, null, -1);
				System.out.println("CuasPoseRT "+float_comparison);
				final IntersceneLmaFloat.Result cuda_result = lmaGpu.execPoseFxJacobian(
						reference_camera, scene_camera,
						reference_xyz, reference_atr, scene_xyz, scene_atr,
						flat_centers, float_selection, true);
				if (cuda_result != null) {
					final IntersceneLmaFloat.ResultComparison cuda_comparison =
							IntersceneLmaFloat.compareResults(cuda_result, float_result);
					System.out.println("CuasPoseRT CUDA vs float getFxDerivs: "+
							cuda_comparison.format("CUDA", "float"));
				}
			}
			// cycle_rms_meas moved after runLma (rung C1): the resident prepare defers
			// the initial RMS to the first step's packed[19,20] - getInitialRms() holds
			// the same "re-measured at this cycle's pose" value on BOTH paths (the
			// legacy prepare seeds it, the resident first step seeds it).
			// By Claude on 07/17/2026 (was getLastRms()[1] here, before runLma).
			// pose_lma_debug >= 2: per-inner-step solver lines; header attributes them to
			// their outer cycle (the solver lines carry no scene/cycle context).
			// By Claude on 07/12/2026, from Andrey's request.
			final int lma_debug = Math.max(debugLevel, clt_parameters.curt.pose_lma_debug);
			if (clt_parameters.curt.pose_lma_debug >= 2) {
				System.out.println("CuasPoseRT innerLMA: scene "+scene.getImageName()+
						" outer cycle "+nlma+" (lambda0 "+lma_lambda+"):");
			}
			if (rtProfile != null) rtProfile.beginLmaRun();
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			final int lmaResult = intersceneLma.runLma(
					lma_lambda, // curt.pose_lambda override (0 = legacy ilp.ilma_lambda) // By Claude on 07/12/2026
					clt_parameters.ilp.ilma_lambda_scale_good,
					clt_parameters.ilp.ilma_lambda_scale_bad,
					clt_parameters.ilp.ilma_lambda_max,
					clt_parameters.ilp.ilma_rms_diff,
					// lean-only inner cap (Andrey 07/13: 1 = validated static shape; the shared
					// imp.max_LMA stays high for varying-scene applications). By Claude on 07/13/2026.
					(clt_parameters.curt.pose_max_lma > 0) ?
							clt_parameters.curt.pose_max_lma : clt_parameters.imp.max_LMA,
					false,                            // last_run
					lma_debug); // curt.pose_lma_debug boost (inner solver only) // By Claude on 07/12/2026
			if (rtProfile != null) rtProfile.endLmaRun(profileStart);
			if (lmaResult < 0) {
				System.out.println("leanFitScene(): LMA failed, lmaResult="+lmaResult);
				PoseCorrExport.sceneFailed(scene); // drop a partial pose_corr capture (armed only) // By Claude on 07/13/2026
				return null;
			}
			profileStart = (rtProfile != null) ? rtProfile.start() : 0L;
			scene_xyzatr0 = intersceneLma.getSceneXYZATR(false);
			final double [] diffs_atr = intersceneLma.getV3Diff(ErsCorrection.DP_DSAZ);
			final double [] diffs_xyz = intersceneLma.getV3Diff(ErsCorrection.DP_DSX);
			last_diff_atr = diffs_atr[0]; // By Claude on 07/12/2026
			cycle_datr[nlma] = diffs_atr[0]; // By Claude on 07/12/2026
			cycle_inner[nlma] = lmaResult;   // inner runLma iterations this cycle // By Claude on 07/12/2026
			cycle_rms_meas[nlma] = intersceneLma.getInitialRms()[1]; // re-measured pure RMS at this cycle's pose (moved after runLma, rung C1) // By Claude on 07/17/2026
			cycle_rms_solved[nlma] = intersceneLma.getLastRms()[1]; // model-predicted pure RMS after the solve // By Claude on 07/12/2026
			ncyc = nlma + 1;                 // By Claude on 07/12/2026
			if (rtProfile != null) rtProfile.addElapsed(RtPoseProfile.POSE_UPDATE, profileStart);
			if ((fixed_cycles <= 0) && // lean v2: fixed count = no early exit // By Claude on 07/12/2026
					(diffs_atr[0] < clt_parameters.imp.exit_change_atr) &&
					(diffs_xyz[0] < clt_parameters.imp.exit_change_xyz)) {
				break;
			}
		}
		// QC diagnostic (fixed-cycle mode): flag scenes whose last ATR change would have
		// missed the legacy convergence threshold - data for choosing between 2 and 3
		// cycles, and a canary for scene classes that ever need more. Never steers the
		// loop. By Claude on 07/12/2026, from Andrey's design.
		if ((fixed_cycles > 0) && !(last_diff_atr < clt_parameters.imp.exit_change_atr) && (debugLevel > -4)) {
			System.out.println("CuasPoseRT QC: scene "+scene.getImageName()+" pose NOT settled after "+
					fixed_cycles+" fixed cycles: last ATR change "+last_diff_atr+
					" >= exit_change_atr "+clt_parameters.imp.exit_change_atr);
		}
		if (ncyc_out != null) ncyc_out[0] = ncyc; // for the one-line scene report // By Claude on 07/13/2026
		// Per-cycle convergence trajectory (the "why does it take that many cycles"
		// investigation, Andrey 07/12/2026): geometric decay = healthy damped iteration,
		// a stall/plateau = something re-anchoring each cycle (pull term, lambda warm-up,
		// prediction). One compact line per scene; gated by pose_lma_debug >= 1 after the
		// 07/13 verbosity reduction (the investigation is closed - cyc= now rides the
		// scene Done line). By Claude on 07/12/2026, gate 07/13.
		if ((clt_parameters.curt.pose_lma_debug >= 1) && (debugLevel > -4)) {
			final StringBuilder tsb = new StringBuilder();
			for (int i = 0; i < ncyc; i++) tsb.append(String.format(" %.3g", cycle_datr[i]));
			final StringBuilder isb = new StringBuilder(); // inner runLma iterations per cycle // By Claude on 07/12/2026
			for (int i = 0; i < ncyc; i++) isb.append(" "+cycle_inner[i]);
			// meas>solved pairs: re-measured pure RMS at cycle start > model-predicted after
			// the solve. Chain-rule error = the NEXT meas bounces back above this solved.
			// By Claude on 07/12/2026, from Andrey's hypothesis.
			final StringBuilder rsb = new StringBuilder();
			for (int i = 0; i < ncyc; i++) rsb.append(String.format(" %.4f>%.4f",
					cycle_rms_meas[i], cycle_rms_solved[i]));
			System.out.println("CuasPoseRT cycles: scene "+scene.getImageName()+
					" ("+ncyc+" of max "+max_cycles+", lambda0 "+lma_lambda+") dATR/cycle ="+tsb+
					"; innerLMA ="+isb+"; RMS meas>solved ="+rsb);
		}
		if (lma_rms != null) {
			final double [] last_rms = intersceneLma.getLastRms();
			lma_rms[0] = last_rms[0];
			if (lma_rms.length > 1) lma_rms[1] = last_rms[1];
			double sw = 0; int nm = 0;
			for (double [] v : cm[1]) if (v != null) { sw += v[2]; nm++; }
			if (lma_rms.length > 2) lma_rms[2] = sw;
			if (lma_rms.length > 3) lma_rms[3] = nm;
		}
		if (coord_motion_rslt != null) {
			for (int i = 0; (i < coord_motion_rslt.length) && (i < cm.length); i++) {
				coord_motion_rslt[i] = cm[i];
			}
		}
		if (debugLevel > -3) {
			System.out.println("Adjusted interscene, iteration="+nlma+", last RMS = "+
					intersceneLma.getLastRms()[0]+" (lean)");
		}
		// D4 one-shot oracle: this scene just ran the host-driven loop with the
		// resident-step results captured - replay it through the DP entry and
		// bit-compare; PASS arms the ONE-entry path from the next scene on.
		// By Claude on 07/17/2026.
		if (dp_capture != null) {
			dpFirstSceneOracle(clt_parameters, image_dtt, center_CLT, scene,
					pXpYD_center, selection, predicted, reg_weights, margin,
					mb_vectors, mb_tau, mb_max_gain, lma_lambda,
					dp_capture, scene_xyzatr0[1], lmaGpu);
		}
		// pose_corr capture complete: all measure<->solve iterations of this scene are
		// recorded - write the test case (armed only). By Claude on 07/13/2026.
		PoseCorrExport.sceneDone(scene);
		return scene_xyzatr0;
	}

	/**
	 * Re-generate per-scene poses against the virtual-center reference, RT-style.
	 *
	 * TILE-CALIBRATION BOOTSTRAP (Andrey's missing-file rule, 07/05/2026): calibrations
	 * are FILE-gated, per scene sequence (the file lives in this sequence's CENTER model
	 * directory). If -POSE-RT-TILE-CALIB is missing (fresh v013_min copy - or DELETED
	 * to force: the ONLY force method, no configuration checkbox), pass 1 runs FULL
	 * (all strength-selected tiles), generates
	 * and saves the calibration, and the sequence AUTOMATICALLY re-runs filtered
	 * (~150 cluster tiles): the first run is slow but ends in the same state as a warmed
	 * system, and all outputs are the filtered-run products (pass-1 outputs are
	 * overwritten; only the calibration file survives from pass 1). Any other sequence
	 * with its calibration already present bypasses the bootstrap entirely.
	 * Force a rebuild by ERASING the file - no configuration changes needed.
	 *
	 * @param clt_parameters processing parameters (uses curt.pose_str threshold,
	 *                       imp.lma_use_R, LMA/offset limits as the offline flow)
	 * @param center_CLT     virtual-center reference: must have CenterClt (fclt) and
	 *                       the final combo DSI restored
	 * @param quadCLTs       scene sequence (time-ascending indices), instances built
	 *                       with image data (e.g. by CuasRanging.prepareFpixels())
	 * @param debugLevel     debug level
	 * @return fitted poses [nscene][2][3] ({xyz,atr}, xyz locked to zero) or null
	 */
	public static double [][][] testPoseSequence(
			final CLTParameters clt_parameters,
			final QuadCLT       center_CLT,
			final QuadCLT []    quadCLTs,
			final int           debugLevel) {
		return testPoseSequence(clt_parameters, center_CLT, quadCLTs, null, debugLevel);
	}

	/** Pose sequence using an optional immutable all-scene raw preload. */
	public static double [][][] testPoseSequence(
			final CLTParameters    clt_parameters,
			final QuadCLT          center_CLT,
			final QuadCLT []       quadCLTs,
			final CuasScenePreload scenePreload,
			final int              debugLevel) {
		final boolean [] saved_calib = new boolean [1];
		double [][][] fitted = poseSequencePass(clt_parameters, center_CLT, quadCLTs,
				scenePreload,
				saved_calib, // out: set when this pass generated+saved the tile calibration
				debugLevel);
		if (saved_calib[0]) { // bootstrap: calibration was just generated - re-run filtered
			System.out.println("CuasPoseRT.testPoseSequence(): BOOTSTRAP - "+TILE_CALIB_SUFFIX+
					" was just generated; re-running the sequence with the filtered selection"+
					" (all outputs are overwritten by the filtered run)");
			fitted = poseSequencePass(clt_parameters, center_CLT, quadCLTs,
					scenePreload,
					null,    // saved_calib: a filtered pass never writes the calibration
					debugLevel);
		}
		return fitted;
	}

	/** The single-pass body of {@link #testPoseSequence}: one FULL or filtered sequence.
	 *  By Claude on 07/05/2026 (bootstrap split). */
	private static double [][][] poseSequencePass(
			final CLTParameters    clt_parameters,
			final QuadCLT          center_CLT,
			final QuadCLT []       quadCLTs,
			final CuasScenePreload scenePreload,
			final boolean []       saved_calib,  // out (may be null): [0]=true if the tile calibration was saved
			final int              debugLevel) {
		final double  min_str =     clt_parameters.curt.pose_str;   // e.g. 1.0
		final int     margin =      clt_parameters.imp.margin;
		final double  mb_tau =      clt_parameters.imp.mb_tau;
		final double  mb_max_gain = clt_parameters.imp.mb_max_gain_inter;
		final double  min_offset =  clt_parameters.imp.min_offset;
		final double  max_offset =  clt_parameters.imp.max_rel_offset *
				center_CLT.getTilesX() * center_CLT.getTileSize();
		final double  max_rms =     clt_parameters.imp.eig_use ?
				clt_parameters.imp.eig_max_rms : clt_parameters.imp.max_rms;
		final double [] reg_weights = clt_parameters.ilp.ilma_regularization_weights;
		final double [] lma_rms = new double [clt_parameters.imp.eig_use ? 6 : 4];

		// 3-angle parameter selection: azimuth, tilt (+ optionally roll), XYZ locked
		// (rotation-only pose: displacements are good enough from the mechanical
		// construction; same as offline CUAS lock_position=true)
		final boolean [] param_select = ErsCorrection.getParamSelect(
				false,                          // use_Z (lock_position)
				clt_parameters.imp.lma_use_R,   // use_R
				false,                          // use_XY (lock_position)
				true,                           // use_AT
				false, false, false);           // ERS az/tilt/roll - off
		if (debugLevel > -3) {
			System.out.print("CuasPoseRT.testPoseSequence(): param_select=[");
			for (boolean b : param_select) System.out.print(b ? "+" : "-");
			System.out.println("], min_str="+min_str+", max_offset="+max_offset);
		}

		// ---- Reference prep (ONCE) - the persistent virtual-center TD reference ----
		final boolean is_aux = center_CLT.isAux();
		final double [][] center_dsi = center_CLT.dsi;
		final double [] strength =      center_dsi[is_aux ? TwoQuadCLT.DSI_STRENGTH_AUX      : TwoQuadCLT.DSI_STRENGTH_MAIN];
		// ONE disparity source for all of RT (Andrey ruling, certified in the render test):
		// the PLAIN slice conditioned to be defined for ALL tiles (sky = infinity/0,
		// negatives clamped). NOT gated by use_lma_dsi (the LMA slice is NaN over the sky).
		// By Claude on 07/05/2026.
		final double [] center_disparity = CuasRender.conditionDisparityCuas(
				center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX : TwoQuadCLT.DSI_DISPARITY_MAIN],
				center_CLT.getTilesX(),               // By Claude on 07/07/2026 (small-FG Laplacian fill)
				clt_parameters.curt.disp_fg_tiles,
				clt_parameters.curt.disp_fg_over);
		final boolean [] reliable_ref = new boolean [strength.length];
		int num_reliable = 0;
		for (int i = 0; i < strength.length; i++) {
			reliable_ref[i] = (strength[i] > min_str) && !Double.isNaN(center_disparity[i]);
			if (reliable_ref[i]) num_reliable++;
		}
		if (debugLevel > -3) {
			System.out.println("CuasPoseRT.testPoseSequence(): num_reliable="+num_reliable+
					" (strength > "+min_str+")");
		}
		// Automatic FPN-style calibration reuse: if -POSE-RT-TILE-CALIB exists (and not forced to
		// recalc), derive the selection from it (per-tile max residual; NMAD outlier gate +
		// rank-N budget) and AND with the strength mask - a filtered run. Otherwise run FULL
		// (all strength-selected tiles) and generate the calibration at the end.
		boolean full_selection = true; // the tile calibration is (re)saved only from a full-selection run
		// pose_full (DEBUG): all strength-selected tiles, calibration neither read nor written
		// (a measurement-debug run must not pollute the persistent tile calibration). By Claude on 07/04/2026.
		// File-gated (no force checkbox - DELETE the file to force a rebuild, Andrey 07/06/2026)
		ImagePlus imp_max = clt_parameters.curt.pose_full ? null :
				center_CLT.readImagePlusFromModelDirectory(TILE_CALIB_SUFFIX);
		if ((imp_max == null) && !clt_parameters.curt.pose_full) { // legacy name fallback
			imp_max = center_CLT.readImagePlusFromModelDirectory(TILE_CALIB_SUFFIX_OLD);
		}
		if (clt_parameters.curt.pose_full) {
			System.out.println("CuasPoseRT.testPoseSequence(): pose_full ON (DEBUG) - using ALL "+num_reliable+
					" strength-selected tiles, tile calibration NOT read/written");
		}
		if (imp_max != null) {
			final float [] fmax = (float []) imp_max.getProcessor().getPixels();
			final boolean [] filt = deriveSelection(
					fmax,
					center_CLT.getTilesX(),
					clt_parameters.curt.pose_dxy_k,
					clt_parameters.curt.pose_num_tiles,
					clt_parameters.curt.pose_shrink, // interior gate (3-A3) // By Claude on 07/13/2026
					debugLevel);
			int num_filt = 0;
			for (int i = 0; i < reliable_ref.length; i++) {
				reliable_ref[i] &= (i < filt.length) && filt[i];
				if (reliable_ref[i]) num_filt++;
			}
			full_selection = false;
			System.out.println("CuasPoseRT.testPoseSequence(): reusing "+TILE_CALIB_SUFFIX+" calibration, filtered to "+
					num_filt+" tiles (of "+num_reliable+" by strength); delete "+
					center_CLT.getImageName()+TILE_CALIB_SUFFIX+".tiff (model dir) to recalculate");
		} else {
			System.out.println("CuasPoseRT.testPoseSequence(): "+TILE_CALIB_SUFFIX+" not found"+
					" - FULL run, will generate the calibration"+
					(clt_parameters.curt.pose_full ? "" : ", then re-run filtered (bootstrap)"));
		}
		final double [][] pXpYD_center = OpticalFlow.transformToScenePxPyD(
				null,             // final Rectangle [] extra_woi,
				center_disparity, // final double []    disparity_ref,
				ZERO3,            // final double []    scene_xyz,
				ZERO3,            // final double []    scene_atr,
				center_CLT,       // final QuadCLT      scene_QuadClt,
				center_CLT);      // final QuadCLT      reference_QuadClt
		final float [][] fclt = new float [][] {center_CLT.getCenterClt()};
		final TpTask [][] tp_tasks_center = Interscene.setReferenceGPU(
				clt_parameters,   // CLTParameters      clt_parameters,
				center_CLT,       // QuadCLT            ref_scene,
				center_disparity, // double []          ref_disparity,
				pXpYD_center,     // double [][]        ref_pXpYD,
				fclt,             // final float [][]   fclt,
				reliable_ref,     // final boolean []   selection,
				margin,           // final int          margin,
				mb_tau,           // double             mb_tau,
				mb_max_gain,      // double             mb_max_gain,
				null,             // double [][]        mb_vectors (center does not move)
				debugLevel);      // int                debug_level
		// The borrowed center arrives ALREADY APPROPRIATED (zero-DC, in memory) from
		// CuasRT.appropriateCenter() - the borrow boundary at RT initialization; the
		// getCenterClt()/setReferenceGPU above therefore uploaded the zeroed reference.
		// Nothing to compensate here. By Claude on 07/05/2026, per Andrey's ruling.

		// ---- Stored poses (comparison truth + seed) ----
		final ErsCorrection ers_center = center_CLT.getErsCorrection();
		final double [][][] stored = new double [quadCLTs.length][][];
		for (int i = 0; i < quadCLTs.length; i++) if (quadCLTs[i] != null) {
			stored[i] = ers_center.getSceneXYZATR(quadCLTs[i].getImageName()); // may be null
		}
		int earliest = -1;
		for (int i = 0; i < quadCLTs.length; i++) if (quadCLTs[i] != null) { earliest = i; break; }
		if (earliest < 0) {
			System.out.println("CuasPoseRT.testPoseSequence(): no scenes");
			return null;
		}
		// Seed: earliest stored pose ("cheating" bootstrap); identity if unavailable.
		// Any scene works as the entry point - every fit is anchored to the virtual
		// center, not chained to neighbors.
		double [][] predicted = (stored[earliest] != null) ?
				new double [][] {stored[earliest][0].clone(), stored[earliest][1].clone()} :
				new double [][] {ZERO3.clone(), ZERO3.clone()};
		if (stored[earliest] == null) {
			System.out.println("CuasPoseRT.testPoseSequence(): WARNING - no stored pose for seed scene "+
					quadCLTs[earliest].getImageName()+", seeding with identity");
		}

		// Angle->pixel conversion, same scales the LMA uses (GeometryCorrection par_scales):
		// az/tilt ~ focal length, roll ~ distortion radius (edge-of-field displacement)
		final double px_per_rad_at = 1000.0 * center_CLT.getGeometryCorrection().focalLength /
				center_CLT.getGeometryCorrection().pixelSize;
		final double px_per_rad_r =  1000.0 * center_CLT.getGeometryCorrection().distortionRadius /
				center_CLT.getGeometryCorrection().pixelSize;
		final double [] px_per_rad = {px_per_rad_at, px_per_rad_at, px_per_rad_r};

		// PER-SEQUENCE DC level for the A2 scene conditioning - NOT per-scene: a per-scene
		// sky median oscillates with rotation (terrain sweeps the sky half; 234-count pk-pk
		// at exactly 1 rev period, POSE-10 forensics) and downstream temporal averages turn
		// it into content ghosts. ONE constant (CuasRT.appropriateCenter derived it from
		// the borrowed center = the average scene). By Claude on 07/05/2026, Andrey's rule.
		final CuasConditioning.Config cond_cfg = CuasConditioning.configFromCurt(clt_parameters.curt); // rowcol_* now dialog-driven (RT step 2a) // By Claude on 07/07/2026
		if (clt_parameters.curt.pose_raw) {
			cond_cfg.dc_level = CuasRT.sequenceDcLevel(clt_parameters, center_CLT);
			if (Double.isNaN(cond_cfg.dc_level)) {
				System.out.println("CuasPoseRT: WARNING - no per-sequence DC level (RT-STATE missing, curt.dc_center==0):"+
						" falling back to the PER-SCENE sky median (rotation-synced DC flicker!)");
			}
		}
		CuasScenePreload activePreload = scenePreload;
		if ((activePreload != null) && Double.isNaN(cond_cfg.dc_level)) {
			System.out.println("CuasPoseRT: ERROR - preloaded RT path requires fixed sequence DC; pose RT measurement NOT RUN because RT-STATE/manual dc_center is unavailable");
			return null;
		}
		final float [][] preloadCpuScratch;
		if (activePreload != null) {
			// Row/Col was either applied from the borrowed profiles during preload or was
			// explicitly disabled because FLIR already supplied it. The timed stage is
			// therefore strictly linear for either CPU or GPU. By Codex on 07/15/2026.
			cond_cfg.rowcol_enable = false;
			preloadCpuScratch = activePreload.createScratch();
			System.out.println("CuasPoseRT: PRELOADED float[scene][sensor][pixel] input active ("+
					(activePreload.isRowColApplied() ? "borrowed Row/Col pre-applied with prior corr-xml scales" :
						"Row/Col bypassed")+
					", timed conditioning="+(cond_cfg.gpu_enable ? "GPU" : "CPU")+")");
		} else {
			preloadCpuScratch = null;
		}
		// D5 overlap state (design ratified 07/17/2026): dp_overlap_scene[0] = the
		// scene index already conditioned into the ACTIVE device image-set ring slot
		// during the previous scene's DP entry (-1 = none); dp_ring_set[0] = the ring
		// slot holding the CURRENT scene's images; dp_overlap_ok[0] = one-time
		// downgrade on any overlap failure (serial order resumes, fail-safe).
		// By Claude on 07/17/2026.
		final int []     dp_overlap_scene = {-1};
		final int []     dp_ring_set      = {0};
		final boolean [] dp_overlap_ok    = {true};
		// ---- Ascending per-scene loop (the RT iterator) ----
		final int tilesX = center_CLT.getTilesX();
		final int tilesY = center_CLT.getTilesY();
		final int num_pix = tilesX * tilesY;
		final ArrayList<float [][]> hyper_scenes = new ArrayList<float [][]>(); // per used scene: [component][tile]
		final ArrayList<String>     hyper_ts =     new ArrayList<String>();
		if (!clt_parameters.imp.eig_use && (debugLevel > -4)) {
			System.out.println("CuasPoseRT.testPoseSequence(): imp.eig_use is OFF - the 4 eigen slices of "+
					"-POSE-RT-HYPER (sqrt_l0, sqrt_l1, elong, eig0_ang) will be all NaN");
		}
		// Phase B lean engine (curt_pose_lean): ImageDtt instance for the convert stage,
		// created once (mirrors interCorrPair's construction)
		final ImageDtt image_dtt_lean = clt_parameters.curt.pose_lean ? new ImageDtt(
				center_CLT.getNumSensors(),
				clt_parameters.transform_size,
				clt_parameters.img_dtt,
				center_CLT.isAux(),
				center_CLT.isMonochrome(),
				center_CLT.isLwir(),
				clt_parameters.getScaleStrength(center_CLT.isAux()),
				center_CLT.getGPU()) : null;
		if (clt_parameters.curt.pose_lean) {
			System.out.println("CuasPoseRT.testPoseSequence(): LEAN measurement engine (phase B v2, "+
					"certified CuasRender chain): TD-average(16) x virtual-center single conj-multiply, "+
					"all-defined conditioned disparity, MB compensation "+
					(clt_parameters.imp.mb_en ? "ON (compare vs MBEN/stored)" : "OFF (compare vs NOMB)"));
		}
		if (clt_parameters.curt.pose_stored) { // By Claude on 07/04/2026
			System.out.println("CuasPoseRT.testPoseSequence(): pose_stored ON (DEBUG) - STORED poses, "+
					"NO LMA: measurement/render chain only"+
					(clt_parameters.curt.pose_lean ? "" : " (only implemented for the LEAN engine - ignored)"));
		}
		// Rung C2: the per-sequence pose "configuration registers" (reference camera
		// block + tile centers) are keyed on the reference ErsCorrection and must be
		// re-armed at every sequence start. By Claude on 07/17/2026.
		IntersceneLma.resetPoseSequenceCache();
		// DEBUG (pose_corr_save): render per-scene pixel-domain correlations vs the virtual
		// center (-POSE-RT-CORR2D, z=scenes, last LMA cycle, 16x16 cells per tile) - the same
		// visual debugging used on the oracle. Lean only: the oracle engine does not expose
		// its correlation tiles. By Claude on 07/04/2026, per Andrey's debug approach.
		final boolean corr_save = clt_parameters.curt.pose_corr_save && clt_parameters.curt.pose_lean;
		if (clt_parameters.curt.pose_corr_save && !clt_parameters.curt.pose_lean) {
			System.out.println("CuasPoseRT.testPoseSequence(): pose_corr_save is only implemented "+
					"for the LEAN engine (pose_lean) - ignored");
		}
		final int corr_size = 2 * com.elphel.imagej.gpu.GPUTileProcessor.DTT_SIZE - 1; // 15
		final ImageStack stack_corr = corr_save ? new ImageStack(tilesX*(corr_size+1), tilesY*(corr_size+1)) : null;
		final double [][][] corr_pd_holder = corr_save ? new double [1][][] : null;
		final float [] corr_nan_slice = corr_save ? new float [tilesX*(corr_size+1)*tilesY*(corr_size+1)] : null;
		if (corr_nan_slice != null) Arrays.fill(corr_nan_slice, Float.NaN);
		// DEBUG (pose_img_save): the composite (TD-averaged, grid-transformed) scenes actually
		// correlated against the virtual center + the center reference render itself.
		// By Claude on 07/04/2026, per Andrey's debug approach.
		final boolean img_save = clt_parameters.curt.pose_img_save && clt_parameters.curt.pose_lean;
		if (clt_parameters.curt.pose_img_save && !clt_parameters.curt.pose_lean) {
			System.out.println("CuasPoseRT.testPoseSequence(): pose_img_save is only implemented "+
					"for the LEAN engine (pose_lean) - ignored");
		}
		final int img_width =  center_CLT.getGPUQuad().getImageWidth();
		final int img_height = center_CLT.getGPUQuad().getImageHeight();
		final ArrayList<float []> img_scenes = img_save ? new ArrayList<float []>() : null;
		final float [][] img_holder = img_save ? new float [1][] : null;
		final float [] img_nan_slice = img_save ? new float [img_width*img_height] : null;
		if (img_nan_slice != null) Arrays.fill(img_nan_slice, Float.NaN);
		// the virtual-center reference render (what every scene correlates against) - becomes
		// the second component of the -POSE-RT-COMPOSITE hyperstack, repeated for every scene
		final float [] ref_render = img_save ? com.elphel.imagej.cuas.CuasMotion.perSensorImagesFromTD(
				center_CLT.getGPUQuad(), true)[0] : null; // reference buffer, all slots = center
		final double [][][] fitted = new double [quadCLTs.length][][];
		final int [] fail_reason = new int [1];
		final StringBuffer sb = new StringBuffer();
		sb.append("index, timestamp, pred_az, pred_tilt, pred_roll, fit_az, fit_tilt, fit_roll,"+
				" stored_az, stored_tilt, stored_roll, dfit_az, dfit_tilt, dfit_roll, rms, weight, num_tiles, fail\n");
		int num_ok = 0, num_fail = 0;
		double [] sum2_d = new double [3];
		double [] max_d =  new double [3];
		int num_cmp = 0;
		double prev_ts = Double.NaN;
		double [][] prev_pose = null;
		// FAKE PREVIOUS pose for the seed scene (Andrey 07/13/2026): step BACK from the
		// first two known stored poses so scene 0 gets real finite-difference rates
		// (uniform MB + ERS) like every other scene, instead of the zero-rate special
		// case (mb=(0,0) left the seed fit carrying the uncompensated v*tau bias).
		// Backward difference of two ACTUAL poses - no hand-derived rotation rate, no
		// sign-convention risk. In RT production a continuous stream always has a real
		// previous frame; this covers batch runs and stream (re)starts, where the
		// nominal-rotation config can feed the same prev_pose/prev_ts seam.
		// Note: changes ONLY the seed scene's rates -> the next full-sequence run is
		// deliberately NOT digit-identical to the 07/13 baseline in scene 0.
		// By Claude on 07/13/2026, from Andrey's design.
		if (stored[earliest] != null) {
			final double ts0 = Double.parseDouble(quadCLTs[earliest].getImageName().replace("_", "."));
			for (int ns2 = earliest + 1; ns2 < quadCLTs.length; ns2++) {
				if ((quadCLTs[ns2] == null) || (stored[ns2] == null)) continue;
				final double ts2 = Double.parseDouble(quadCLTs[ns2].getImageName().replace("_", "."));
				if (!(ts2 > ts0)) break; // non-monotonic names - keep the legacy zero-rate seed
				prev_ts = ts0 - (ts2 - ts0);
				prev_pose = new double [][] {predicted[0].clone(), new double [3]};
				for (int j = 0; j < 3; j++) { // rate at the seed = exactly (stored[ns2]-stored[earliest])/(ts2-ts0)
					prev_pose[1][j] = predicted[1][j] - (stored[ns2][1][j] - stored[earliest][1][j]);
				}
				if (debugLevel > -4) {
					System.out.println(String.format(
							"CuasPoseRT.testPoseSequence(): FAKE previous for seed scene %s from stored poses"+
							" %s/%s: atr_dt=(%.4g, %.4g, %.4g) rad/s",
							quadCLTs[earliest].getImageName(), quadCLTs[earliest].getImageName(),
							quadCLTs[ns2].getImageName(),
							(stored[ns2][1][0] - stored[earliest][1][0]) / (ts2 - ts0),
							(stored[ns2][1][1] - stored[earliest][1][1]) / (ts2 - ts0),
							(stored[ns2][1][2] - stored[earliest][1][2]) / (ts2 - ts0)));
				}
				break;
			}
		}
		RtPoseTiming rtTiming = null;
		RtPoseProfile rtProfile = null;
		RT_POSE_PROFILE.remove();
		if (activePreload != null) {
			int rtInputs = 0;
			for (int i = earliest; i < quadCLTs.length; i++) if (quadCLTs[i] != null) rtInputs++;
			if (cond_cfg.gpu_enable && !CuasConditioning.preparePreloadedConditioning(
					quadCLTs[earliest], activePreload.getScene(earliest), cond_cfg)) {
				System.out.println("CuasPoseRT: WARNING - requested GPU linear conditioning is unavailable for this configuration; timed preload path will use the CPU oracle");
			}
			rtTiming = new RtPoseTiming(clt_parameters.curt.pose_pace, rtInputs);
			final int profileCycles = (clt_parameters.curt.pose_cycles > 0) ?
					clt_parameters.curt.pose_cycles : clt_parameters.imp.max_cycles;
			rtProfile = new RtPoseProfile(rtInputs, profileCycles);
			RT_POSE_PROFILE.set(rtProfile);
		}
		for (int nscene = earliest; nscene < quadCLTs.length; nscene++) {
			if (quadCLTs[nscene] == null) continue;
			if (rtTiming != null) rtTiming.beforeScene();
			final String ts_name = quadCLTs[nscene].getImageName();
			final double ts = Double.parseDouble(ts_name.replace("_", "."));
			// ERS velocities from finite differences of the pose sequence (substitute
			// for the offline PIMU rates; only the residual/wobble rates ~0.05 rad/s -
			// ERS params are not fitted, disable_ers=true below)
			double [][] dxyzatr_dt = new double [][] {ZERO3.clone(), ZERO3.clone()};
			if ((prev_pose != null) && !Double.isNaN(prev_ts) && (ts > prev_ts)) {
				for (int j = 0; j < 3; j++) {
					dxyzatr_dt[1][j] = (predicted[1][j] - prev_pose[1][j]) / (ts - prev_ts);
				}
			}
			// pose_stored (DEBUG): use the STORED per-scene ERS rates (xyz_dt AND atr_dt), the
			// same source and calls as the render path (renderSceneSequence) - so the decoupled
			// measurement/render applies IDENTICAL ERS to the CUAS-MERGED-CUAS-DBG renders.
			// Live mode keeps finite-difference ATR rates (xyz_dt=0) - the ErsCorrection-vs-
			// render divergence under test. By Claude on 07/04/2026, from Andrey's diagnosis.
			double [] stored_ers_xyz_dt = null, stored_ers_atr_dt = null;
			if (clt_parameters.curt.pose_stored) {
				stored_ers_xyz_dt = ers_center.getSceneErsXYZ_dt(ts_name);
				stored_ers_atr_dt = ers_center.getSceneErsATR_dt(ts_name);
			}
			if ((stored_ers_xyz_dt != null) && (stored_ers_atr_dt != null)) {
				quadCLTs[nscene].getErsCorrection().setErsDt(
						stored_ers_xyz_dt,   // double []    ers_xyz_dt,
						stored_ers_atr_dt);  // double []    ers_atr_dt - exactly as renderSceneSequence
			} else {
				if (clt_parameters.curt.pose_stored && (debugLevel > -4)) {
					System.out.println("CuasPoseRT: scene "+nscene+" ("+ts_name+
							") pose_stored: no stored ERS rates, falling back to finite differences");
				}
				quadCLTs[nscene].getErsCorrection().setErsDt(
						QuadCLTCPU.scaleDtToErs(clt_parameters, dxyzatr_dt));
			}
			// Phase A2 ingest (curt_pose_raw): read RAW /jp4/, condition with the CURRENT
			// calibration and force-upload straight to the GPU - bypasses the prepared
			// image_data (old broken Photogrammetric Calibration). The bayer guard keeps
			// the upload alive through interCorrPair's own setBayerImages(false).
			// A2 results legitimately diverge from phase A (cleaner input) - judge by own
			// dstored quality. By Claude on 07/05/2026, from Andrey's design.
			if (clt_parameters.curt.pose_raw) {
				final long rtConditionStart = (rtTiming != null) ? rtTiming.conditionStart() : 0L;
				final boolean raw_ok;
				if (dp_overlap_scene[0] == nscene) {
					// D5: this scene was conditioned into the active ring slot during the
					// previous scene's DP entry - nothing to do here. By Claude on 07/17/2026.
					dp_overlap_scene[0] = -1;
					raw_ok = true;
				} else {
					raw_ok = (activePreload != null) ?
						CuasConditioning.conditionPreloadedSceneToGpu(
								quadCLTs[nscene], activePreload.getScene(nscene), preloadCpuScratch, cond_cfg) :
						CuasConditioning.conditionSceneToGpu(
								quadCLTs[nscene],           // QuadCLT scene (jp4 + calibration + FPN)
								cond_cfg,                   // Config: defaults + the PER-SEQUENCE dc_level // By Claude on 07/05/2026
								ImageDtt.THREADS_MAX,       // read threads
								debugLevel - 2);            // debug
				}
				if (rtTiming != null) rtTiming.conditionDone(rtConditionStart);
				if (!raw_ok) {
					System.out.println("CuasPoseRT: scene "+nscene+" ("+ts_name+
							") raw-jp4 ingest FAILED - coasting prediction");
					fitted[nscene] = new double [][] {predicted[0].clone(), predicted[1].clone()};
					num_fail++;
					// keep CSV/hyper alignment: record an empty measurement row
					final float [][] nan_comps = new float [HYPER_COMPONENTS.length][num_pix];
					for (float [] c : nan_comps) Arrays.fill(c, Float.NaN);
					hyper_scenes.add(nan_comps);
					hyper_ts.add(ts_name);
					if (stack_corr != null) stack_corr.addSlice(ts_name, corr_nan_slice.clone()); // keep z aligned // By Claude on 07/04/2026
					if (img_scenes != null) img_scenes.add(img_nan_slice.clone()); // By Claude on 07/04/2026
					sb.append(nscene+", "+ts_name);
					for (int j = 0; j < 12; j++) sb.append(", NaN");
					sb.append(", NaN, 0.0, 0, -1\n");
					prev_pose = predicted;
					prev_ts = ts;
					if (rtTiming != null) rtTiming.finishScene(false);
					continue;
				}
			}
			final long profilePostStart = (rtProfile != null) ? rtProfile.start() : 0L;
			long profileStageStart = (rtProfile != null) ? rtProfile.start() : 0L;
			// Motion-blur compensation (gated by the same imp.mb_en as offline): per-tile blur
			// vectors from the finite-difference rates -> interCorrPair takes the
			// setInterTasksMotionBlur/interCorrTDMotionBlur path (convert_direct runs twice,
			// the second run subtracts the shifted+scaled copy via negative TpTask.scale -
			// removes the LWIR bolometer exponential tail). mb_en OFF -> single-run path (A/B).
			double [][] mb_vectors_scene = null;
			if (clt_parameters.imp.mb_en) {
				if (clt_parameters.curt.pose_mb_uniform) {
					// lean v2: single shared vector for all tiles (CUAS constant-rpm flow;
					// roll component and wobble ignored by ruling). By Claude on 07/12/2026.
					mb_vectors_scene = uniformMotionBlur(
							center_CLT,          // QuadCLT        center_CLT,
							quadCLTs[nscene],    // QuadCLT        scene,
							pXpYD_center,        // double [][]    pXpYD_center,
							predicted[0],        // double []      camera_xyz,
							predicted[1],        // double []      camera_atr,
							dxyzatr_dt[0],       // double []      camera_xyz_dt,
							dxyzatr_dt[1],       // double []      camera_atr_dt,
							debugLevel - 2);     // int            debugLevel
					if (mb_vectors_scene == null) {
						System.out.println("CuasPoseRT: scene "+ts_name+
								" uniform MB degenerate - falling back to per-tile getMotionBlur");
					} else if (clt_parameters.curt.pose_lma_debug >= 1) {
						// verbosity reduction 07/13: mb=() now rides the scene Done line
						System.out.println(String.format(
								"CuasPoseRT: scene %s uniform MB vector = (%.4f, %.4f) px/s",
								ts_name, mb_vectors_scene[0][0], mb_vectors_scene[1][0]));
					}
					// B2 MB-descriptor oracle (one-shot): evaluate the LEGACY one-point
					// getMotionBlur (IntersceneLma prepareLMA path) at the SAME
					// representative point and compare against the fast serial
					// getMotionBlurPoint the production path just used. Identical double
					// math - the gate expectation is max|d| == 0 exactly; also times both
					// (design gate: MB setup ~6 -> <0.5 ms/scene). By Claude on 07/16/2026.
					if ((clt_parameters.curt.pose_lma_debug >= 1) && !b2_mb_reported &&
							(mb_vectors_scene != null)) {
						b2_mb_reported = true;
						final long b2_t0 = System.nanoTime();
						uniformMotionBlur(center_CLT, quadCLTs[nscene], pXpYD_center,
								predicted[0], predicted[1], dxyzatr_dt[0], dxyzatr_dt[1],
								debugLevel - 2); // warm repeat of the FAST path (B2 timing gate)
						final long b2_t1 = System.nanoTime();
						final double [] rep_point = mbRepresentativePoint(center_CLT, pXpYD_center);
						final double [][] one_point = {{rep_point[0], rep_point[1], rep_point[2]}};
						final double [][] mb_legacy = OpticalFlow.getMotionBlur(
								center_CLT, quadCLTs[nscene], one_point,
								predicted[0], predicted[1], dxyzatr_dt[0], dxyzatr_dt[1],
								0, debugLevel - 2);
						final long b2_t2 = System.nanoTime();
						final double d_max = ((mb_legacy == null) ||
								Double.isNaN(mb_legacy[0][0]) || Double.isNaN(mb_legacy[1][0])) ?
								Double.NaN :
								Math.max(Math.abs(mb_vectors_scene[0][0] - mb_legacy[0][0]),
										Math.abs(mb_vectors_scene[1][0] - mb_legacy[1][0]));
						System.out.println(String.format(
								"CuasPoseRT B2 MB descriptor oracle (one-shot): fast=%.3f ms,"+
								" legacy getMotionBlur=%.3f ms; max|d|=%.3g px/s (gate: 0 exactly -"+
								" same double chain; any diff = a real divergence)",
								(b2_t1-b2_t0)*1e-6, (b2_t2-b2_t1)*1e-6, d_max));
					}
				}
				if (mb_vectors_scene == null) { // legacy per-tile path (pose_mb_uniform OFF or degenerate fallback) // By Claude on 07/12/2026
					mb_vectors_scene = OpticalFlow.getMotionBlur(
							center_CLT,          // QuadCLT        ref_scene,
							quadCLTs[nscene],    // QuadCLT        scene,
							pXpYD_center,        // double [][]    ref_pXpYD,
							predicted[0],        // double []      camera_xyz,
							predicted[1],        // double []      camera_atr,
							dxyzatr_dt[0],       // double []      camera_xyz_dt,
							dxyzatr_dt[1],       // double []      camera_atr_dt,
							0,                   // int            shrink_gaps,
							debugLevel - 2);     // int            debug_level
				}
			}
			if (rtProfile != null) rtProfile.addElapsed(RtPoseProfile.MOTION_BLUR, profileStageStart);
			fail_reason[0] = 0;
			final int [] ncyc_scene = {0}; // outer cycles executed (lean path; 0 = other paths) // By Claude on 07/13/2026
			final double [][][] coord_motion_rslt = new double [3][][]; // [centers, vector_XYS, eigen] of the last LMA cycle
			if (corr_pd_holder != null) corr_pd_holder[0] = null; // no stale slices on failure // By Claude on 07/04/2026
			if (img_holder != null) img_holder[0] = null; // By Claude on 07/04/2026
			double [][] pose;
			profileStageStart = (rtProfile != null) ? rtProfile.start() : 0L;
			if (clt_parameters.curt.pose_lean && clt_parameters.curt.pose_stored) {
				// DEBUG (pose_stored): measurement/render at the STORED pose, NO LMA - decouples
				// the task/render/correlation chain from the solver. The composite must then match
				// the oracle CUAS-MERGED-CUAS-DBG renders tile-for-tile. By Claude on 07/04/2026.
				final double [][] use_pose = (stored[nscene] != null) ? stored[nscene] : predicted;
				if (stored[nscene] == null) {
					System.out.println("CuasPoseRT: scene "+nscene+" ("+ts_name+") pose_stored: no stored pose, using prediction");
				}
				final double [][][] cm = leanMeasure(
						clt_parameters, image_dtt_lean, center_CLT, quadCLTs[nscene],
						center_disparity, pXpYD_center, reliable_ref,
						use_pose[0], use_pose[1], margin,
						mb_vectors_scene, mb_tau, mb_max_gain, // MB per imp.mb_en // By Claude on 07/05/2026
						corr_pd_holder, img_holder, debugLevel - 2);
				if (cm != null) {
					pose = new double [][] {use_pose[0].clone(), use_pose[1].clone()};
					for (int i = 0; (i < coord_motion_rslt.length) && (i < cm.length); i++) {
						coord_motion_rslt[i] = cm[i];
					}
					double sw = 0, s2 = 0; int nm = 0;
					for (double [] v : cm[1]) if (v != null) {
						sw += v[2];
						s2 += v[2]*(v[0]*v[0] + v[1]*v[1]);
						nm++;
					}
					lma_rms[0] = (sw > 0) ? Math.sqrt(s2/sw) : Double.NaN; // weighted RMS of measured offsets
					if (lma_rms.length > 2) lma_rms[2] = sw;
					if (lma_rms.length > 3) lma_rms[3] = nm;
				} else {
					pose = null;
					fail_reason[0] = 1;
				}
			} else if (clt_parameters.curt.pose_lean) {
				// D5 overlap (design ratified 07/17/2026): pre-condition the NEXT scene
				// into the other device image-set ring slot while THIS scene's DP entry
				// runs (dpSceneEntry invokes the runnable between launch and collect -
				// all its GPU work rides the copy stream). Preloaded harness only (file
				// I/O must never enter the overlap window). On the blocking/host-driven
				// paths the runnable never runs and the next scene conditions serially
				// as before. Any failure restores the previous set and disables the
				// overlap for the rest of the run (fail-safe). By Claude on 07/17/2026.
				Runnable dp_overlap_work = null;
				if (dp_overlap_ok[0] && clt_parameters.curt.pose_h2d_overlap &&
						clt_parameters.curt.pose_raw && (activePreload != null) &&
						center_CLT.getGPUQuad().supportsPoseSceneDpOverlap()) {
					int nn = -1;
					for (int j = nscene + 1; j < quadCLTs.length; j++) {
						if (quadCLTs[j] != null) { nn = j; break; }
					}
					if (nn >= 0) {
						final int next_scene = nn;
						dp_overlap_work = () -> {
							final int target_set = dp_ring_set[0] ^ 1;
							if (CuasConditioning.conditionPreloadedSceneToGpuOverlap(
									quadCLTs[next_scene], activePreload.getScene(next_scene),
									preloadCpuScratch, cond_cfg, target_set, dp_ring_set[0])) {
								dp_ring_set[0] = target_set;
								dp_overlap_scene[0] = next_scene;
							} else {
								dp_overlap_ok[0] = false;
								System.out.println("CuasPoseRT: D5 overlap conditioning DISABLED for this run - serial order resumes");
							}
						};
					}
				}
				pose = leanFitScene(
						clt_parameters,      // CLTParameters  clt_parameters,
						image_dtt_lean,      // ImageDtt       image_dtt,
						center_CLT,          // QuadCLT        center_CLT,
						quadCLTs[nscene],    // QuadCLT        scene,
						center_disparity,    // double []      center_disparity,
						pXpYD_center,        // double [][]    pXpYD_center,
						reliable_ref,        // boolean []     selection,
						predicted,           // double [][]    predicted (initial + pull),
						param_select,        // boolean []     param_select,
						reg_weights,         // double []      reg_weights,
						margin,              // int            margin,
						mb_vectors_scene,    // double [][]    mb_vectors (MB per imp.mb_en - lean v2) // By Claude on 07/05/2026
						mb_tau,              // double         mb_tau, // By Claude on 07/05/2026
						mb_max_gain,         // double         mb_max_gain, // By Claude on 07/05/2026
						lma_rms,             // double []      lma_rms (out),
						coord_motion_rslt,   // double [][][]  coord_motion_rslt (out),
						corr_pd_holder,      // double [][][]  corr_pd_out (last-cycle PD correlations) // By Claude on 07/04/2026
						img_holder,          // float [][]     img_out (last-cycle composite render) // By Claude on 07/04/2026
						ncyc_scene,          // int []         ncyc_out (outer cycles, for the one-line report) // By Claude on 07/13/2026
						dp_overlap_work,     // Runnable       dp_overlap_work (D5: condition the next scene under the DP entry) // By Claude on 07/17/2026
						debugLevel - 2);     // int            debug_level
				if (pose == null) fail_reason[0] = 1; // measurement/LMA
			} else {
			pose = Interscene.adjustPairsLMAInterscene(
					clt_parameters,      // CLTParameters  clt_parameters,
					true,                // boolean        initial_adjust,
					false,               // boolean        fpn_disable,
					true,                // boolean        disable_ers,
					new double [] {min_offset, max_offset, 0.0}, // double [] min_max,
					fail_reason,         // int []         fail_reason,
					center_CLT,          // QuadCLT        ref_QuadClt (virtual center)
					center_disparity,    // double []      ref_disparity,
					center_CLT,          // QuadCLT        first_QuadClt (virtual center)
					pXpYD_center,        // double [][]    pXpYD_ref,
					clt_parameters.imp.cuas_min_confidence, // double min_confidence (as re-adjust: final-DSI vintage)
					reliable_ref,        // boolean []     reliable_ref,
					null,                // boolean []     reliable_all,
					tp_tasks_center[0],  // TpTask[]       tp_tasks_ref,
					quadCLTs[nscene],    // QuadCLT        scene_QuadClt,
					predicted,           // double [][]    camera_xyzatr (initial),
					predicted,           // double [][]    scene_xyzatr_pull,
					new double [2][3],   // double [][]    ref_xyzatr,
					param_select,        // boolean[]      param_select,
					reg_weights,         // double []      param_regweights,
					lma_rms,             // double []      rms_out,
					max_rms,             // double         max_rms,
					mb_tau,              // double         mb_tau,
					mb_max_gain,         // double         mb_max_gain,
					mb_vectors_scene,    // double [][]    mb_vectors (null when imp.mb_en OFF - single-run path),
					coord_motion_rslt,   // double [][][]  coord_motion_rslt (last-cycle measurement, for -POSE-RT-HYPER),
					debugLevel - 2);     // int            debug_level
			}
			if (rtProfile != null) rtProfile.addElapsed(RtPoseProfile.LEAN_FIT, profileStageStart);
			profileStageStart = (rtProfile != null) ? rtProfile.start() : 0L;
			final boolean ok = (pose != null);
			if (ok) {
				fitted[nscene] = pose;
				num_ok++;
			} else {
				// coast: keep the prediction as this scene's pose, continue the sequence
				fitted[nscene] = new double [][] {predicted[0].clone(), predicted[1].clone()};
				num_fail++;
				if (debugLevel > -4) {
					System.out.println("CuasPoseRT: scene "+nscene+" ("+ts_name+") FAILED, reason="+
							fail_reason[0]+" ("+Interscene.getFailReason(fail_reason[0])+"), coasting prediction");
				}
			}
			// Collect measurement characterization slices for -POSE-RT-HYPER
			{
				final float [][] comps = new float [HYPER_COMPONENTS.length][num_pix];
				for (float [] c : comps) Arrays.fill(c, Float.NaN);
				final double [][] vxys = coord_motion_rslt[1]; // {dx,dy,strength} per tile or null
				final double [][] eig =  (coord_motion_rslt.length > 2) ? coord_motion_rslt[2] : null;
				if (vxys != null) {
					for (int i = 0; i < num_pix; i++) {
						if (vxys[i] != null) {
							comps[0][i] = (float) vxys[i][0];
							comps[1][i] = (float) vxys[i][1];
							comps[2][i] = (float) vxys[i][2];
							comps[3][i] = (float) Math.sqrt(vxys[i][0]*vxys[i][0] + vxys[i][1]*vxys[i][1]);
						}
						if ((eig != null) && (eig[i] != null)) { // {eig_x, eig_y, l0, l1}, l0 <= l1
							final double l0 = eig[i][2], l1 = eig[i][3];
							comps[4][i] = (float) Math.sqrt(l0);
							comps[5][i] = (float) Math.sqrt(l1);
							comps[6][i] = (l0 > 0) ? (float) Math.sqrt(l1/l0) : Float.NaN;
							double ang = Math.atan2(eig[i][1], eig[i][0]); // precise-axis direction
							if (ang < 0) ang += Math.PI;                   // fold to [0, PI)
							comps[7][i] = (float) ang;
						}
					}
				}
				hyper_scenes.add(comps);
				hyper_ts.add(ts_name);
			}
			// DEBUG: render this scene's PD correlations into the -POSE-RT-CORR2D stack // By Claude on 07/04/2026
			if (stack_corr != null) {
				if (corr_pd_holder[0] != null) {
					final double [][] dbg_corr = ImageDtt.corr_partial_dbg(
							new double [][][] {corr_pd_holder[0]},   // [layer][tile][15*15]
							tilesX,                                  // tilesX
							corr_size,                               // corr_size (15)
							clt_parameters.corr_border_contrast,     // border_contrast
							debugLevel);                             // globalDebugLevel
					final float [] fslice = new float [dbg_corr[0].length];
					for (int i = 0; i < fslice.length; i++) fslice[i] = (float) dbg_corr[0][i];
					stack_corr.addSlice(ts_name, fslice);
				} else {
					stack_corr.addSlice(ts_name, corr_nan_slice.clone());
				}
			}
			// DEBUG: the composite scene render for -POSE-RT-COMPOSITE // By Claude on 07/04/2026
			if (img_scenes != null) {
				img_scenes.add((img_holder[0] != null) ? img_holder[0] : img_nan_slice.clone());
			}
			// CSV row + comparison stats
			final double [] st = (stored[nscene] != null) ? stored[nscene][1] : null;
			sb.append(nscene+", "+ts_name);
			for (int j = 0; j < 3; j++) sb.append(String.format(", %.6f", predicted[1][j]));
			for (int j = 0; j < 3; j++) sb.append(String.format(", %.6f", fitted[nscene][1][j]));
			for (int j = 0; j < 3; j++) sb.append((st != null) ? String.format(", %.6f", st[j]) : ", NaN");
			for (int j = 0; j < 3; j++) {
				double d = (st != null) ? (fitted[nscene][1][j] - st[j]) : Double.NaN;
				sb.append((st != null) ? String.format(", %.6f", d) : ", NaN");
				if (ok && (st != null)) {
					sum2_d[j] += d * d;
					if (Math.abs(d) > max_d[j]) max_d[j] = Math.abs(d);
				}
			}
			if (ok && (stored[nscene] != null)) num_cmp++;
			sb.append(String.format(", %.6f, %.1f, %.0f, %d\n",
					ok ? lma_rms[0] : Double.NaN, lma_rms[2], lma_rms[3], fail_reason[0]));
			// Scene identification line - attributes the preceding (unlabeled) LMA iteration
			// prints from adjustPairsLMAInterscene to this scene (index of N + timestamp)
			if (debugLevel > -4) {
				StringBuffer sl = new StringBuffer();
				sl.append("CuasPoseRT scene "+nscene+" (of "+quadCLTs.length+") "+ts_name+
						(ok ? " Done." : " FAILED (coasted).")+
						String.format(" RMS=%.4f, weight=%.1f, num=%.0f", lma_rms[0], lma_rms[2], lma_rms[3]));
				// one-line verbosity (Andrey 07/13): cycles + uniform MB vector fold into the
				// scene line (the separate trace/MB lines are now pose_lma_debug >= 1 only)
				if (ncyc_scene[0] > 0) sl.append(", cyc="+ncyc_scene[0]);
				if ((mb_vectors_scene != null) && clt_parameters.curt.pose_mb_uniform) {
					sl.append(String.format(", mb=(%.1f, %.1f)", mb_vectors_scene[0][0], mb_vectors_scene[1][0]));
				}
				if (st != null) {
					sl.append(" dstored[pix]=[");
					for (int j = 0; j < 3; j++) {
						sl.append(String.format((j > 0 ? ", %7.4f" : "%7.4f"),
								(fitted[nscene][1][j] - st[j]) * px_per_rad[j]));
					}
					sl.append("]");
				}
				System.out.println(sl.toString());
			}
			// zero-order prediction for the next scene
			prev_pose = predicted;
			prev_ts = ts;
			predicted = new double [][] {fitted[nscene][0].clone(), fitted[nscene][1].clone()};
			if (rtProfile != null) {
				rtProfile.addElapsed(RtPoseProfile.REPORT_QC, profileStageStart);
				rtProfile.addElapsed(RtPoseProfile.POST_TOTAL, profilePostStart);
			}
			if (rtTiming != null) rtTiming.finishScene(true);
			// pose_corr kernel-test run (roadmap 3-C rung 1): the case is written once the
			// first fitted scene completes - the exclusive diagnostic needs no more scenes
			// (this run's CSV/products are partial and disposable). By Claude on 07/13/2026.
			if (PoseCorrExport.captureDone()) {
				System.out.println("CuasPoseRT: pose_corr kernel-test case captured after scene "+
						nscene+" ("+ts_name+") - stopping the sequence early");
				break;
			}
		}
		if (rtTiming != null) rtTiming.printSummary(num_fail);
		if (rtProfile != null) rtProfile.printSummary();
		RT_POSE_PROFILE.remove();

		// ---- Output ----
		final String csv_path = center_CLT.getX3dDirectory(true) + Prefs.getFileSeparator() +
				center_CLT.getImageName() + "-POSE-RT-TEST.csv";
		try (FileWriter fw = new FileWriter(csv_path)) {
			fw.write(sb.toString());
			System.out.println("CuasPoseRT.testPoseSequence(): saved "+csv_path);
		} catch (IOException e) {
			System.out.println("CuasPoseRT.testPoseSequence(): FAILED to save "+csv_path+": "+e.getMessage());
		}
		// reliable_tiles selection mask (1=selected), same grid as the DSI
		{
			final float [] frel = new float [num_pix];
			for (int i = 0; i < num_pix; i++) frel[i] = reliable_ref[i] ? 1.0f : 0.0f;
			final ImagePlus imp_rel = new ImagePlus(center_CLT.getImageName()+"-POSE-RT-RELIABLE",
					new FloatProcessor(tilesX, tilesY, frel));
			center_CLT.saveImagePlusInModelDirectory(null, imp_rel); // title as filename
		}
		// -POSE-RT-TILE-CALIB calibration (was -POSE-RT-MAXDXY): per-tile max-over-scenes residual, +inf where any
		// scene was NaN (auto-reject and mergeable across runs by simple max), NaN where
		// not selected/measured. Saved only from a FULL-selection run so a filtered run
		// never shrinks the calibration coverage. The derived boolean selection is also
		// saved (-POSE-RT-RELIABLE-FILT) for visualization.
		if (full_selection && !clt_parameters.curt.pose_full && !hyper_scenes.isEmpty()
				&& !PoseCorrExport.captureDone()) { // pose_full: never write the calibration // By Claude on 07/04/2026; a pose_corr capture stops the sequence early - a truncated max-over-scenes calibration must never be saved // By Claude on 07/13/2026
			final float [] fmax = new float [num_pix];
			Arrays.fill(fmax, Float.NaN);
			for (int i = 0; i < num_pix; i++) if (reliable_ref[i]) {
				float mx = 0f;
				boolean has = false, has_nan = false;
				for (float [][] comps : hyper_scenes) {
					final float d = comps[3][i]; // dxy
					if (Float.isNaN(d)) has_nan = true;
					else { has = true; if (d > mx) mx = d; }
				}
				// NaN (not +inf) where any scene was NaN: deriveSelection rejects NaN and +inf
				// identically (non-finite), and NaN keeps the saved TIFF viewable in ImageJ
				// (+inf breaks min/max autoscaling). Unmeasured/unselected tiles are also NaN.
				fmax[i] = (has_nan || !has) ? Float.NaN : mx;
			}
			center_CLT.saveImagePlusInModelDirectory(null, new ImagePlus(
					center_CLT.getImageName()+TILE_CALIB_SUFFIX, new FloatProcessor(tilesX, tilesY, fmax)));
			if (saved_calib != null) saved_calib[0] = true; // triggers the bootstrap filtered re-run // By Claude on 07/05/2026
			final boolean [] keep = deriveSelection(
					fmax,
					tilesX,
					clt_parameters.curt.pose_dxy_k,
					clt_parameters.curt.pose_num_tiles,
					clt_parameters.curt.pose_shrink, // interior gate (3-A3) // By Claude on 07/13/2026
					debugLevel);
			final float [] fkeep = new float [num_pix];
			for (int i = 0; i < num_pix; i++) fkeep[i] = keep[i] ? 1.0f : 0.0f;
			center_CLT.saveImagePlusInModelDirectory(null, new ImagePlus(
					center_CLT.getImageName()+"-POSE-RT-RELIABLE-FILT", new FloatProcessor(tilesX, tilesY, fkeep)));
		}
		// -POSE-RT-CORR2D (DEBUG): per-scene pixel-domain correlations vs the virtual center // By Claude on 07/04/2026
		if ((stack_corr != null) && (stack_corr.getSize() > 0)) {
			final ImagePlus imp_corr = new ImagePlus(center_CLT.getImageName()+"-POSE-RT-CORR2D", stack_corr);
			imp_corr.getProcessor().resetMinAndMax();
			center_CLT.saveImagePlusInModelDirectory(null, imp_corr); // title as filename
		}
		// -POSE-RT-COMPOSITE (DEBUG): hyperstack [t: transformed-real, virtual][z: scene] -
		// the composite (TD-averaged, grid-transformed) scene actually correlated and the
		// virtual-center render it correlates against (same for all scenes, repeated so the
		// component slider blink-compares at any scene). Same z as -POSE-RT-HYPER/-CORR2D.
		// By Claude on 07/04/2026, per Andrey's layout.
		if ((img_scenes != null) && !img_scenes.isEmpty()) {
			final ImageStack stack_img = new ImageStack(img_width, img_height);
			for (int ns = 0; ns < img_scenes.size(); ns++) {
				stack_img.addSlice("real:"+hyper_ts.get(ns), img_scenes.get(ns));
			}
			for (int ns = 0; ns < img_scenes.size(); ns++) {
				stack_img.addSlice("virtual:"+hyper_ts.get(ns), ref_render);
			}
			final ImagePlus imp_img = new ImagePlus(center_CLT.getImageName()+"-POSE-RT-COMPOSITE", stack_img);
			imp_img.setDimensions(1, img_scenes.size(), 2); // z=scenes, t=[real, virtual]
			imp_img.setOpenAsHyperStack(true);
			imp_img.getProcessor().resetMinAndMax();
			center_CLT.saveImagePlusInModelDirectory(null, imp_img); // title as filename
		}
		// -POSE-RT-HYPER: z = scenes (top slider), t = components (bottom slider) -
		// same layout as renderSceneSequence make_hyper (component is the outer slice order)
		if (!hyper_scenes.isEmpty()) {
			final ImageStack stack_hyper = new ImageStack(tilesX, tilesY);
			for (int nc = 0; nc < HYPER_COMPONENTS.length; nc++) {
				for (int ns = 0; ns < hyper_scenes.size(); ns++) {
					stack_hyper.addSlice(HYPER_COMPONENTS[nc]+":"+hyper_ts.get(ns), hyper_scenes.get(ns)[nc]);
				}
			}
			final ImagePlus imp_hyper = new ImagePlus(center_CLT.getImageName()+"-POSE-RT-HYPER", stack_hyper);
			imp_hyper.setDimensions(1, hyper_scenes.size(), HYPER_COMPONENTS.length);
			imp_hyper.setOpenAsHyperStack(true);
			imp_hyper.getProcessor().resetMinAndMax();
			center_CLT.saveImagePlusInModelDirectory(null, imp_hyper); // title as filename
		}
		System.out.println("CuasPoseRT.testPoseSequence(): scenes OK="+num_ok+", failed(coasted)="+num_fail+
				", compared="+num_cmp);
		if (num_cmp > 0) {
			final String [] names = {"azimuth", "tilt", "roll"};
			for (int j = 0; j < 3; j++) {
				final double rms_rad = Math.sqrt(sum2_d[j]/num_cmp);
				System.out.println(String.format(
						"  fitted-stored %8s: RMS=%7.4f pix, max=%7.4f pix   (%8.4f mrad, max %8.4f mrad)",
						names[j], rms_rad*px_per_rad[j], max_d[j]*px_per_rad[j],
						1000*rms_rad, 1000*max_d[j]));
			}
			System.out.println(String.format(
					"  (pixel scales: az/tilt %.1f pix/rad = focal/pixelSize, roll %.1f pix/rad = distortionRadius/pixelSize)",
					px_per_rad_at, px_per_rad_r));
		}
		return fitted;
	}
}
