"""
Chunk-block streaming search_and_aggregate — adaptive nd variant.

Differences from disk_aggregation_chunk.py
-------------------------------------------
* Per-chunk result streaming: sums are written into pts_source[sum_radius_names]
  immediately after each row chunk finishes (not all at once at the end), so peak
  accumulator memory is one chunk rather than all source points.
* Per-chunk adaptive nd selection via best_nd_tag (nd_choice.py).

Architecture
------------
All target points are sorted once by (row, col, subcell_nr) into
pts_vals_xy_full / tgt_rows_full / tgt_cols_full / tgt_scnr_full.
Source points are sorted by (row, col, region_and_trgl_id) as usual.

Source rows are processed in chunks.  For each chunk of source rows
[s_lo, s_hi], the only target points needed are those in rows
[s_lo - r_rows, s_hi + r_rows].  These are a contiguous slice of
pts_vals_xy_full (because it is sorted by row).  Two integer pointers
(blk_lo, blk_hi) advance monotonically through the full array —
no hash table, no eviction bookkeeping, no power-of-2 overallocation.

Within a block the contain path looks up precomputed cell sums directly
from grid.sums_array via codec key (one vectorised searchsorted call).
The overlap path gathers raw target points from the block using
block_cell_slices — a dict keyed by absolute codec int covering level-0
cells and all sub-cell levels up to nest_depth (built by _nest_block_slices
via quadtree descent on tgt_scnr_full).  Contain and overlap sub-cells are
disjoint by construction, matching disk_aggregation.py behaviour.

Chunk size is determined by an L3-budget calculation over sorted row costs.
"""

import itertools
import math
import time
from sys import getsizeof as _sys_getsizeof
import numpy as np
from numpy import (
    zeros as _np_zeros,
    flatnonzero as _np_flatnonzero,
    append as _np_append,
    ones as _np_ones,
    searchsorted as _np_searchsorted,
    exp as _np_exp,
    array as _np_array,
)
from numpy.linalg import norm as np_norm
from aabpl.utils.progress import SearchProgress, progress_print
from aabpl.testing.test_performance import time_func_perf
from aabpl import config as _cfg
from ..point_assignment import cell_count_iter
from .nd_choice import best_nd_tag as _best_nd_tag
from .nd_choice import best_nd_tag_weighted as _best_nd_tag_weighted
from .nd_choice import cost_at_array as _nd_cost_at_array
from aabpl.utils.dist_matrix import batched_disk_sum as _batched_disk_sum

def _find_disjoint_clouds(x, y, r):
    """Connected-components labeling of points into spatially disjoint
    clouds, via a coarse r-sized grid + Chebyshev-1 cell adjacency (safe
    superset of "within r": any two points within r of each other are
    guaranteed to land in Chebyshev-adjacent or identical cells at this
    cell size). Dependency-free (no scipy) union-find over the sparse set
    of OCCUPIED cells only -- cost scales with occupied-cell count, never
    with the spatial extent/gap between clouds.

    Returns point_labels: int array, one label per input point (same order
    as x/y), with points in the same disjoint cloud sharing a label.
    """
    cell = r
    cx = np.floor(x / cell).astype(np.int64)
    cy = np.floor(y / cell).astype(np.int64)
    cy_min = cy.min()
    width = int(cy.max() - cy_min) + 3
    cy_off = (cy - cy_min).astype(np.int64)
    key_all = cx * width + cy_off

    # int32 keys sort ~25% faster than int64 (measured: 62.5ms -> 46.9ms on a
    # 521k-point dataset -- this np.unique call is 65-83% of the whole
    # function's cost, dwarfing the union-find over the much smaller
    # occupied-cell count). Only safe to downcast when the actual key range
    # fits -- datasets with a huge spatial extent relative to r could still
    # need int64, so check bounds first rather than assuming.
    # small margin so the later (ucx+dx)*width+(ucy+dy) neighbor-offset
    # shifts (dx,dy in {-1,0,1}) can't push a value past int32 range either.
    _key_min, _key_max = int(key_all.min()), int(key_all.max())
    if -2**31 + width < _key_min and _key_max < 2**31 - width:
        key_all = key_all.astype(np.int32)

    unique_keys, inverse = np.unique(key_all, return_inverse=True)
    n_cells = len(unique_keys)
    ucx = unique_keys // width
    ucy = unique_keys % width

    parent = np.arange(n_cells, dtype=np.int64)

    def find(a):
        while parent[a] != a:
            parent[a] = parent[parent[a]]
            a = parent[a]
        return a

    # Only 4 of the 8 Chebyshev-1 neighbor offsets are needed: every
    # unordered adjacent cell pair is found from exactly one of its two
    # cells via one of these "forward" offsets (dx>0, or dx==0 and dy>0) --
    # the other 4 (their exact opposites) would only rediscover the SAME
    # edge from the other side, pure wasted searchsorted/mask work. Every
    # discovered pair is unioned regardless of index order -- no src<dst
    # filter here (that was only valid when BOTH directions of a pair were
    # searched; with just the forward half, a pair can appear with
    # src>dst and would be silently dropped -- i.e. a real missed union --
    # if filtered the same way).
    for dx, dy in ((1, -1), (1, 0), (1, 1), (0, 1)):
        shifted = (ucx + dx) * width + (ucy + dy)
        idx = np.searchsorted(unique_keys, shifted)
        idx_clip = np.clip(idx, 0, n_cells - 1)
        found = unique_keys[idx_clip] == shifted
        src = np.nonzero(found)[0]
        dst = idx_clip[found]
        for a, b in zip(src.tolist(), dst.tolist()):
            ra, rb = find(a), find(b)
            if ra != rb:
                parent[ra] = rb

    # Vectorized path-compression flattening (pointer doubling): repeatedly
    # jump parent -> parent[parent] until it stops changing -- converges in
    # O(log(longest chain)) numpy-vectorized passes instead of an O(n_cells)
    # Python loop calling find() one cell at a time. Order-independent (the
    # final flattened root is the same regardless of how it's reached), so
    # this is a pure speedup, not a behavior change.
    while True:
        _next_parent = parent[parent]
        if np.array_equal(_next_parent, parent):
            break
        parent = _next_parent

    _, comp_ids = np.unique(parent, return_inverse=True)
    return comp_ids[inverse]


def _label_components_from_cell_keys(unique_keys, width):
    """Same union-find core as _find_disjoint_clouds, but operating directly
    on an already-deduped, already-sorted array of row-major cell keys
    (cr*width+cc) -- e.g. the coarse density-map's `_ck_unique`. No point
    data needed: the sparse occupied-cell set itself is the input, so this
    is effectively free to compute alongside a density map that's already
    built. Chebyshev-1 adjacency between cells (cell size here is whatever
    grid `unique_keys`/`width` were built at -- typically already ~r-sized).

    Returns comp_ids: int array, one label per entry in unique_keys (same
    order), with row-major-adjacent occupied cells sharing a label.
    """
    n_cells = len(unique_keys)
    if n_cells == 0:
        return np.empty(0, dtype=np.int64)
    ucx = unique_keys // width
    ucy = unique_keys % width

    parent = np.arange(n_cells, dtype=np.int64)

    def find(a):
        while parent[a] != a:
            parent[a] = parent[parent[a]]
            a = parent[a]
        return a

    for dx in (-1, 0, 1):
        for dy in (-1, 0, 1):
            if dx == 0 and dy == 0:
                continue
            shifted = (ucx + dx) * width + (ucy + dy)
            idx = np.searchsorted(unique_keys, shifted)
            idx_clip = np.clip(idx, 0, n_cells - 1)
            found = unique_keys[idx_clip] == shifted
            src = np.nonzero(found)[0]
            dst = idx_clip[found]
            mask = src < dst
            for a, b in zip(src[mask].tolist(), dst[mask].tolist()):
                ra, rb = find(a), find(b)
                if ra != rb:
                    parent[ra] = rb

    for i in range(n_cells):
        parent[i] = find(i)

    _, comp_ids = np.unique(parent, return_inverse=True)
    return comp_ids


OVERLAP_BLOCK    = 256
ENABLE_COL_SPLIT  = True   # set False to disable column splitting (for benchmarking)
ENABLE_TALL_STRIPS = False  # allow strips taller than L3 budget when col-split compensates

# ---- adaptive-nd test flags ------------------------------------------------
# When _TEST_RANDOM_ND=True, each col-chunk picks a random nest_depth in
# 0.._TEST_ND_MAX (capped to native nest_depth).  Validates correctness by
# comparing against VALIDATE brute-force; set False to use only the native nd.
_TEST_RANDOM_ND = False
_TEST_ND_MIN    = -1      # min nd to sample (negative = super-cells; -1 groups 2×2 level-0 cells)
_TEST_ND_MAX    = 3       # max nd to sample randomly
# L2/L3 cache budgets are read from config at call time (_cfg.L2_BYTES / _cfg.L3_BYTES)
# so runtime changes to config take effect without reloading this module.
# L2_BYTES: col-split sub-budget (each col block should fit in L2 for the
#   distance-check OVERLAP_BLOCK loop).
# L3_BYTES: primary chunk-row budget — source-point block fits in L3 so
#   pre-aggregation and overlap scatter stay cache-hot.

from typing import NamedTuple

class _AreaWeightOpts(NamedTuple):
    area_weight:       object  # str or None, normalised
    quad_segs:         int
    exact_block_k:     int
    vec_exact:         bool
    keep_raw:          bool
    bnd_frac:          float
    bnd_frac_explicit: bool
    do_weight:         bool
    do_logit:          bool
    do_per_cell:       bool
    do_binary:         bool


def _parse_area_weight(area_weight, cells_rndm_sample) -> '_AreaWeightOpts':
    """Normalise and parse the area_weight option string into structured flags.

    Handles deprecated aliases, comma sub-options (quad_segs, keep_raw, block_k,
    vec), the optional =fraction suffix, and the cells_rndm_sample short-circuit.
    Returns an _AreaWeightOpts namedtuple; all callers should unpack immediately.
    """
    # All cells sampled → every disk is fully valid, no weighting needed.
    if isinstance(cells_rndm_sample, bool) and cells_rndm_sample:
        area_weight = None

    # Deprecated value aliases.
    _ALIASES = {'precise': 'exact', 'estimate': 'logit', 'estimate=0.5': 'logit=0.5'}
    if area_weight in _ALIASES:
        area_weight = _ALIASES[area_weight]

    # Comma sub-options, e.g. 'exact,quad_segs=8'.
    # block_k and vec=0 are undocumented benchmarking backdoors.
    quad_segs     = 8
    exact_block_k = 4      # benchmark confirmed block_k=4 is best
    vec_exact     = True   # vectorised path always used
    keep_raw      = False
    if isinstance(area_weight, str) and ',' in area_weight:
        parts       = [p.strip() for p in area_weight.split(',')]
        area_weight = parts[0]
        for part in parts[1:]:
            if part.startswith('quad_segs='):
                quad_segs = int(part.split('=', 1)[1])
            elif part == 'keep_raw':
                keep_raw = True
            elif part.startswith('block_k='):
                exact_block_k = max(1, int(part.split('=', 1)[1]))
            elif part == 'vec=0':
                vec_exact = False

    # Optional =fraction suffix, e.g. 'logit=0.5', 'flat=0.3'.
    bnd_frac          = 0.5
    bnd_frac_explicit = False
    if isinstance(area_weight, str) and '=' in area_weight:
        area_weight, fstr = area_weight.split('=', 1)
        bnd_frac          = float(fstr)
        bnd_frac_explicit = True

    _VALID = ('exact', 'logit', 'flat', 'binary', None)
    if area_weight not in _VALID:
        progress_print(f"area_weight={area_weight!r} unknown; ignoring. "
                       f"Valid options: {_VALID[:-1]}")
        area_weight = None

    return _AreaWeightOpts(
        area_weight       = area_weight,
        quad_segs         = quad_segs,
        exact_block_k     = exact_block_k,
        vec_exact         = vec_exact,
        keep_raw          = keep_raw,
        bnd_frac          = bnd_frac,
        bnd_frac_explicit = bnd_frac_explicit,
        do_weight         = area_weight is not None,
        do_logit          = area_weight in ('logit', 'flat', 'binary'),
        do_per_cell       = area_weight == 'logit',
        do_binary         = area_weight == 'binary',
    )


@time_func_perf
def search_and_aggregate(
    grid,
    pts_source,
    r,
    c=[],
    y='proj_lat',
    x='proj_lon',
    off_x='offset_x',
    off_y='offset_y',
    pts_target=None,
    row_name='id_y',
    col_name='id_x',
    cell_region_name='cell_region',
    suffix=None,
    exclude_self=True,
    silent=False,
    validate=False,
    area_weight=None,  # None | 'exact' | 'logit' | 'flat' | 'binary'
    plot_pt_disk=None,       # ignored; accepted for API compatibility with orig
    plan_only=False,  # if True: run planning only, return (depth_by_block,
                       # needs_level0) for aggregate_point_data_to_cells_adaptive_nd
                       # to consume, without touching grid.sums_array (may not
                       # exist yet) or executing any chunk.
    run_aggregation=False,  # DIAGNOSTIC/experimental, default off: if True, call
                             # aggregation inline (after planning) then fall
                             # through to execute in this same call, instead of
                             # the normal two-call plan_only flow. Under active
                             # debugging -- do not rely on this for correctness yet.
):
    _t_func_start = time.process_time()
    _single_region = _cfg.SINGLE_REGION
    if pts_target is None:
        pts_target = pts_source

    # ---- grid internals ---------------------------------------------------------
    codec                      = grid._search_internals.cell_codec
    grid_spacing               = grid._search_internals.spacing
    nest_depth                 = grid._search_internals.nest_depth
    contain_region_mult        = grid._search_class.contain_region_mult
    shared_cntd_cells          = grid._search_class.shared_cntd_cells
    cntd_cells_by_region       = grid._search_class.region_and_trgl_id_to_distinct_cntd_cells
    ovlpd_cells_by_region      = grid._search_class.region_and_trgl_id_to_distinct_ovlpd_cells
    ovlpd_cells_by_cell_region = grid._search_class.region_id_to_ovlpd_cells

    n_pts           = len(pts_source)
    n_c             = len(c)
    r2              = r * r
    full_disk_area  = math.pi * r2
    zero_sum        = _np_zeros(n_c, dtype=float)

    # Rough spatial extent, used only by _ensure_deep_nd's cost/benefit check
    # below (predict_timing needs SOME width/height estimate) -- not worth
    # the cost of compute_spatial_stats' skewness computation for a rarely-
    # taken decision, so this stays a coarse bounds-based approximation.
    _dnd_bounds = grid._search_internals.bounds
    _dnd_spatial_width  = max(_dnd_bounds.xmax - _dnd_bounds.xmin, 1e-9)
    _dnd_spatial_height = max(_dnd_bounds.ymax - _dnd_bounds.ymin, 1e-9)

    # ---- parse / normalise area_weight -----------------------------------------
    cells_rndm_sample = grid._search_internals.cells_rndm_sample
    (area_weight, _quad_segs, _exact_block_k, _vec_exact, _keep_raw,
     _bnd_frac, _bnd_frac_explicit, _do_weight, _do_logit, _do_per_cell, _do_binary
    ) = _parse_area_weight(area_weight, cells_rndm_sample)



    # ---- valid-area setup (only when weighting requested) ----------------------
    if _do_weight:
        pad = -int(-r // grid_spacing)
        _row_lo = int(grid._search_internals.row_ids.min()) - pad
        _row_hi = int(grid._search_internals.row_ids.max()) + pad
        _col_lo = int(grid._search_internals.col_ids.min()) - pad
        _col_hi = int(grid._search_internals.col_ids.max()) + pad
        boundary_frac_raw = getattr(grid._search_internals, 'boundary_cell_valid_fraction', {})

        _sa = getattr(grid, 'study_area', None)
        if _sa is not None:
            _sp_va  = grid_spacing
            _x0_va  = grid._search_internals.bounds.xmin
            _y0_va  = grid._search_internals.bounds.ymin
            # Use cached search-grid classification from intersect_polygon_with_grid
            # (avoids rebuilding Shapely geometry objects every perform_search call).
            _cached_key = getattr(grid._search_internals, 'va_search_grid_key', None)
            _use_cache  = _cached_key == (_x0_va, _y0_va, _sp_va)
            if _use_cache:
                _va_fully  = grid._search_internals.va_fully_rc   # set of (r,c)
                _va_partly = grid._search_internals.va_partly_rc  # set of (r,c) or None
                _search_rc = set(
                    (r, c) for r in range(_row_lo, _row_hi + 1)
                           for c in range(_col_lo, _col_hi + 1)
                )
                if _do_binary:
                    _invalid_cells_rc = _search_rc - _va_fully
                elif _va_partly is not None:
                    _invalid_cells_rc = _search_rc - _va_fully - _va_partly
                else:
                    # partly not cached (binary mode was used in intersect call) — recompute
                    _use_cache = False
            if not _use_cache:
                try:
                    from shapely import box as _shp_box
                    from shapely import contains as _shp_contains
                    from shapely import intersects as _shp_intersects
                    from shapely import prepare as _shp_prepare
                    _shp_prepare(_sa)
                    _rr_va = np.arange(_row_lo, _row_hi + 1)
                    _cc_va = np.arange(_col_lo, _col_hi + 1)
                    _RR_va, _CC_va = np.meshgrid(_rr_va, _cc_va, indexing='ij')
                    _rf_va = _RR_va.ravel(); _cf_va = _CC_va.ravel()
                    _boxes_va = _shp_box(
                        _x0_va + _cf_va * _sp_va, _y0_va + _rf_va * _sp_va,
                        _x0_va + (_cf_va + 1) * _sp_va, _y0_va + (_rf_va + 1) * _sp_va,
                    )
                    _fin = _shp_contains(_sa, _boxes_va)
                    if _do_binary:
                        _invalid_cells_rc = set(
                            (int(_rf_va[i]), int(_cf_va[i])) for i in range(len(_rf_va))
                            if not _fin[i]
                        )
                        _partly_cells_rc = set()
                    else:
                        _pin = ~_fin & _shp_intersects(_sa, _boxes_va)
                        _invalid_cells_rc = set(
                            (int(_rf_va[i]), int(_cf_va[i])) for i in range(len(_rf_va))
                            if not _fin[i] and not _pin[i]
                        )
                        _partly_cells_rc = set(
                            (int(_rf_va[i]), int(_cf_va[i])) for i in range(len(_rf_va))
                            if _pin[i]
                        )
                except ImportError:
                    _invalid_cells_rc = set(
                        (int(rr), int(cc))
                        for rr in range(_row_lo, _row_hi + 1)
                        for cc in range(_col_lo, _col_hi + 1)
                        if (int(rr), int(cc)) not in cells_rndm_sample
                    )
        else:
            # No sample area: data-density fallback (same as orig)
            _invalid_cells_rc = set(
                (int(rr), int(cc))
                for rr in range(_row_lo, _row_hi + 1)
                for cc in range(_col_lo, _col_hi + 1)
                if (int(rr), int(cc)) not in cells_rndm_sample
            )

        invalid_cell_keys = set(int(codec.key(0, rr, cc)) for rr, cc in _invalid_cells_rc)
        boundary_cell_fracs = {int(codec.key(0, int(rr), int(cc))): float(f)
                               for (rr, cc), f in boundary_frac_raw.items()}
        # If compute_fractions=False, boundary_frac_raw is empty; build boundary_cell_keys from:
        # (1) partly_cells_rc computed in-line above (non-cache path), or
        # (2) va_partly_rc stored by intersect_polygon_with_grid (cache path).
        if not boundary_cell_fracs and _sa is not None:
            partial_cells_rc = locals().get('_partly_cells_rc') or getattr(grid._search_internals, 'va_partly_rc', None) or set()
            boundary_cell_keys = set(int(codec.key(0, rr, cc)) for rr, cc in partial_cells_rc)
        else:
            boundary_cell_keys = set(boundary_cell_fracs)
        get_cell_centroid = grid._search_internals.cell_centroid
        _cntd_cells_by_cell_region    = grid._search_class.region_id_to_cntd_cells
        _ovlpd_cells_by_cell_region_va = grid._search_class.region_id_to_ovlpd_cells
        # Level-0 codec-offset arrays keyed by cell_region_id (mirrors orig _l0_offset).
        # nested_cntd_cells / nested_ovlpd_cells store subcell offsets as fractional
        # grid-spacing units (e.g. level-1 at ±0.25/±0.75, level-3 at ±0.125 etc.).
        #
        # Valid-area accounting works at level-0 granularity: a cell is either fully
        # inside, a boundary cell, or outside the study area — sub-cell resolution
        # doesn't change that classification.  _l0_cntd_only / _l0_all_parents
        # intentionally stay at level-0 for this reason.
        def _l0_cntd_only(cells):
            """Level-0 entries only — whole cells guaranteed in disk."""
            if isinstance(cells, np.ndarray) and len(cells):
                mask = cells[:, 0] == 0
                l0 = cells[mask]
                return codec.offset_int(l0) if len(l0) else codec.offset_int(np.empty((0, 3), dtype=np.float32))
            return codec.offset_int(np.empty((0, 3), dtype=np.float32))

        def _l0_all_parents(cells):
            """Map every subcell to its parent level-0 cell (floor(dr+0.5)), deduplicated."""
            if isinstance(cells, np.ndarray) and len(cells):
                l0_rc = np.floor(cells[:, 1:] + 0.5).astype(np.int64)
                unique_rc = np.unique(l0_rc, axis=0)
                l0_arr = np.zeros((len(unique_rc), 3), dtype=np.float32)
                l0_arr[:, 1] = unique_rc[:, 0]
                l0_arr[:, 2] = unique_rc[:, 1]
                return codec.offset_int(l0_arr)
            return codec.offset_int(np.empty((0, 3), dtype=np.float32))

        contain_l0_offsets  = {rid: _l0_cntd_only(c2) for rid, c2 in _cntd_cells_by_cell_region.items()}
        # For overlap: union of parent cells from cntd subcells and ovlpd subcells,
        # minus the level-0 cells already handled by the contain path.
        contain_l0_offset_sets = {rid: set(int(k) for k in arr) for rid, arr in contain_l0_offsets.items()}
        def _ovlpd_parents(cntd_cells, ovlpd_cells, cntd_excl):
            all_cells = np.vstack([cntd_cells, ovlpd_cells]) if len(cntd_cells) and len(ovlpd_cells) else (
                cntd_cells if len(cntd_cells) else ovlpd_cells)
            raw = _l0_all_parents(all_cells)
            if cntd_excl and len(raw):
                raw = raw[~np.isin(raw, list(cntd_excl))]
            return raw
        overlap_l0_offsets = {rid: _ovlpd_parents(
                _cntd_cells_by_cell_region.get(rid, np.empty((0,3), np.float32)),
                _ovlpd_cells_by_cell_region_va.get(rid, np.empty((0,3), np.float32)),
                contain_l0_offset_sets.get(rid, set()))
            for rid in set(list(_cntd_cells_by_cell_region) + list(_ovlpd_cells_by_cell_region_va))}
        # single-region fast path: region_id_to_cntd/ovlpd_cells are empty dicts,
        # so contain/overlap_l0_offsets[0] would KeyError in the main loop.
        # Populate region 0 from shared_cntd_cells + single_region_ovlpd_cells.
        if _single_region and 0 not in contain_l0_offsets:
            contain_l0_offsets[0]  = _l0_cntd_only(grid._search_class.shared_cntd_cells)
            overlap_l0_offsets[0]  = _l0_all_parents(grid._search_class.single_region_ovlpd_cells)
            contain_l0_offset_sets[0] = set(int(k) for k in contain_l0_offsets[0])
        va_contain_cache = {}   # (home_key_int, cell_region_id) -> invalid_area_scalar
        _cell_area    = grid_spacing * grid_spacing
        # Half-diagonal of a grid cell: maximum distance from centroid to any corner.
        # Used as a cheap prefilter: centroid_dist > r + _half_diag guarantees no intersection.
        _half_diag    = grid_spacing * 0.5 * 2 ** 0.5
        if _do_logit:
            _logit_Q = 1 / (0.70628102 + _np_exp(0.57266908 * (grid_spacing / r - 2)))
            _logit_B = 1 / (-0.21443453 + _np_exp(0.76899004 * (grid_spacing / r - 2)))
        # Resolve _bnd_frac (valid fraction for boundary cells, flat/logit/smooth/exact)
        # and _non_fully_frac (valid fraction for all non-fully-inside cells, binary).
        # Priority: (1) explicit =f from user, (2) pre-sampled in intersect_polygon_with_grid,
        # (3) legacy per-call sample from _bkeys_frac, (4) default.
        _non_fully_frac = 0.0   # binary: average valid fraction of non-fully-inside cells
        if _do_weight and not _bnd_frac_explicit:
            if _do_binary:
                _non_fully_frac = getattr(grid._search_internals, 'non_fully_valid_fraction', 0.0)
            elif area_weight != 'logit':
                _pre = getattr(grid._search_internals, 'bnd_frac_sampled', None)
                if _pre is not None:
                    _bnd_frac = _pre
                elif boundary_cell_fracs:
                    _sample = list(boundary_cell_fracs.values())[:100]
                    _bnd_frac = float(np.mean(_sample))
        elif _do_binary and _bnd_frac_explicit:
            _non_fully_frac = _bnd_frac  # user said binary=0.4 → 0.4 valid fraction

    # ---- integer offset templates (absolute codec keys, all levels) -------------
    shared_cntd_offset     = codec.offset_int(shared_cntd_cells)
    cntd_offset_by_region  = {rid: codec.offset_int(cells) for rid, cells in cntd_cells_by_region.items()}
    ovlpd_offset_by_region = {rid: codec.offset_int(cells) for rid, cells in ovlpd_cells_by_region.items()}

    # ---- single-region template (undocumented backdoor: cfg.SINGLE_REGION=True) ----
    # Precomputed in build_region_cell_lookups (cached in disk_region_cache).
    # Contain = shared_cntd_offset; Overlap = all distinct_cntd + nested_ovlpd across
    # all regions, expanded to nest_depth level so granularities match, minus shared.
    # Always built (not gated on cfg.SINGLE_REGION): adaptive per-chunk nd selection
    # can dispatch a single_region=True tag (sT, s1T, smNT) on any chunk regardless
    # of that global flag, so the template needs to exist unconditionally.
    _sr_ovlpd_n_cells = 0  # used below to size candidate_buffer correctly
    single_region_ovlpd_offset = codec.offset_int(
        grid._search_class.single_region_ovlpd_cells)
    _sr_ovlpd_n_cells = len(single_region_ovlpd_offset)

    # ---- multi-nd precomputation (always: needed for adaptive best_nd_tag) -------
    # Build geometry for nd = 0 .. min(_TEST_ND_MAX, native_nd).
    # For each nd: offset arrays for shared contain and per-region contain/overlap.
    # At nd < native_nd: downgrade from native; at native_nd: reuse existing dicts.
    from .disk_geometry import (build_disk_region_lookups as _build_lookups,
                                downgrade_disk_region_cache_entry as _downgrade,
                                _evict_disk_region_cache as _evict_disk_cache)
    _adaptive_nd_entries = {}  # nd -> cache entry dict (shared_cntd_cells, ...)
    _nd_max = nest_depth if not _TEST_RANDOM_ND else min(_TEST_ND_MAX, nest_depth)

    # Persist every downgraded intermediate level into disk_region_cache too,
    # not just the local _adaptive_nd_entries dict -- otherwise a depth only
    # ever reached via downgrade is invisible to later requests for that
    # exact depth (different r, same sr ratio -- routine, since
    # FIXED_SPACING_RATIO pins every call to the same ratio by default),
    # which then trigger a full from-scratch pipeline build instead of
    # reusing the downgrade. Confirmed this session (2026-07): the same
    # nominal depth reached via these two different construction routes
    # (downgrade vs full build), purely depending on incidental call
    # order/cache state, produced genuinely different aggregated sums
    # between an otherwise-identical non-disjoint/disjoint comparison.
    # "Worth computing" is currently always-true (every intermediate level
    # between two needed depths gets filled+cached) -- this is the hook for
    # a future cost/benefit gate on intermediate levels specifically, should
    # that ever matter; for now the cost of a downgrade step is cheap enough
    # (measured this session: ~1-11s per level, vs ~2s-8min+ for a full
    # build at the same depth) that skipping it isn't worth the complexity.
    _dnd_ratio_key = round(r / grid_spacing, 8)
    # Fields build_disk_region_lookups computes from the region BOUNDARY
    # geometry (which triangle-1 region a fractional point-offset falls
    # into) -- a question about arc/line boundaries in continuous offset
    # space, entirely independent of nest_depth (nest_depth only controls
    # how finely a region's CONTAINED cells get subdivided, not the region
    # boundaries themselves). downgrade_disk_region_cache_entry doesn't
    # recompute these (not needed by its own caller), so they're carried
    # over unchanged from the source entry being downgraded -- making the
    # downgraded result a COMPLETE entry, safe to cache under the normal
    # disk_region_cache key namespace like any full build.
    _DND_SR_ONLY_FIELDS = (
        'raster_cell_to_region_comb_nr', 'offset_region_comb_nr_to_check',
        'offset_all_x_vals', 'offset_all_y_vals', 'contain_region_mult',
    )
    def _downgrade_and_cache(_nd, _from_nd):
        _dnd_cache_key = (_dnd_ratio_key, _nd, False)
        if _dnd_cache_key in _cfg.disk_region_cache:
            _entry = _cfg.disk_region_cache[_dnd_cache_key]
        else:
            _from_entry = _adaptive_nd_entries[_from_nd]
            _entry = _downgrade(_from_entry, _from_nd, grid_spacing=grid_spacing, r=r)
            for _fld in _DND_SR_ONLY_FIELDS:
                if _fld in _from_entry:
                    _entry[_fld] = _from_entry[_fld]
            _cfg.disk_region_cache[_dnd_cache_key] = _entry
            _evict_disk_cache()
        _adaptive_nd_entries[_nd] = _entry
        return _entry

    # Build/fetch top entry (already cached from grid setup)
    _top_entry = _build_lookups(None, grid_spacing=grid_spacing, r=r,
                                nest_depth=nest_depth, silent=True)
    _adaptive_nd_entries[nest_depth] = _top_entry
    for _nd in range(nest_depth - 1, -1, -1):
        _downgrade_and_cache(_nd, _nd + 1)
    # Build per-nd offset lookup tables
    _nd_shared_cntd  = {}   # nd -> codec offset array
    _nd_cntd_by_reg  = {}   # nd -> {rid: codec offset array}
    _nd_ovlpd_by_reg = {}   # nd -> {rid: codec offset array}
    _nd_sr_ovlpd     = {}   # nd -> codec offset array (single-region only)
    _nd_sr_ovlpd_n   = {}   # nd -> int (for buffer sizing)
    for _nd in range(_nd_max + 1):
        _e = _adaptive_nd_entries[_nd]
        _nd_shared_cntd[_nd]  = codec.offset_int(_e['shared_cntd_cells'])
        _nd_cntd_by_reg[_nd]  = {rid: codec.offset_int(cells)
                                 for rid, cells in _e['region_and_trgl_id_to_distinct_cntd_cells'].items()}
        _nd_ovlpd_by_reg[_nd] = {rid: codec.offset_int(cells)
                                 for rid, cells in _e['region_and_trgl_id_to_distinct_ovlpd_cells'].items()}
        _sr_arr = _e.get('single_region_ovlpd_cells')
        if _sr_arr is not None and len(_sr_arr):
            _nd_sr_ovlpd[_nd]   = codec.offset_int(_sr_arr)
        else:
            _nd_sr_ovlpd[_nd]   = single_region_ovlpd_offset
        _nd_sr_ovlpd_n[_nd] = len(_nd_sr_ovlpd[_nd])
    _nd_range_test   = list(range(max(_TEST_ND_MIN, -nest_depth), _nd_max + 1))
    _nd_chunk_counts = {}   # (nd, single_region) tag -> int count
    if _nd_sr_ovlpd_n:
        _sr_ovlpd_n_cells = max(_sr_ovlpd_n_cells, max(_nd_sr_ovlpd_n.values()))

    # ---- precomputed sums array (from aggregate_point_data_to_cells) -----------
    # Contain path: instead of summing block_cell_sums in a Python loop, index
    # directly into the contiguous _sums_array using codec keys → one numpy call.
    # This block moved from here (function top) to right after the inline
    # aggregation call below -- see that call site for why.

    # ---- target point arrays sorted by (row, col, subcell_nr) -------------------
    # subcell_nr orders points within a cell by quadtree quadrant at each level,
    # enabling _nest_block_slices to slice sub-cell ranges via searchsorted.
    _xmin = grid._search_internals.bounds.xmin
    _ymin = grid._search_internals.bounds.ymin
    _ox = 0.5 + ((pts_target[x].values - _xmin) % grid_spacing - grid_spacing / 2) / grid_spacing
    _oy = 0.5 + ((pts_target[y].values - _ymin) % grid_spacing - grid_spacing / 2) / grid_spacing
    _tgt_scnr = np.zeros(len(pts_target), dtype=np.int64)
    for _i in range(1, nest_depth + 1):
        _n2 = 2**_i; _m2 = 2**((nest_depth - _i) * 2)
        _tgt_scnr += ((_ox // (1 / _n2)) % _n2 % 2 * _m2 +
                      (_oy // (1 / _n2)) % _n2 % 2 * 2 * _m2).astype(np.int64)
    _sort_idx    = np.lexsort((_tgt_scnr,
                               pts_target[col_name].values,
                               pts_target[row_name].values))
    tgt_rows_full    = pts_target[row_name].values[_sort_idx].astype(np.int64)
    tgt_cols_full    = pts_target[col_name].values[_sort_idx].astype(np.int64)
    tgt_scnr_full    = _tgt_scnr[_sort_idx]
    pts_vals_xy_full = pts_target[c + [x, y]].values[_sort_idx]

    n_tgt             = len(tgt_rows_full)
    tgt_row_lo_global = int(tgt_rows_full[0])  if n_tgt else 0
    tgt_row_hi_global = int(tgt_rows_full[-1]) if n_tgt else 0

    # ---- r_rows: grid-cell reach of a disk of radius r -------------------------
    r_rows = int(math.ceil(r / grid_spacing))
    _sr    = r / grid_spacing  # spacing ratio for nd_choice lookup

    # ---- fine-cell gather margin needed for a given (possibly super-cell) nd ---
    # r_rows above only accounts for the native (fine) grid_spacing. A super-cell
    # (nd<0) chunk's overlap template reaches farther in fine-cell terms
    # (effective coarse_spacing = grid_spacing * 2**k), so the row-band AND
    # column candidate-gather windows (need_row_lo/hi, tgt_col_lo/hi below) must
    # widen to that chunk's own reach or its overlap candidates outside the
    # window are silently never gathered -- not because the underlying target
    # points don't exist, but because they were never loaded into the local
    # block. Computed per (row-)strip from that strip's own resolved nd tags
    # (_sp_nd_tag/_sp_hot_tag, already known from planning -- no extra work),
    # not from the deepest tag anywhere in the whole benchmark table: using the
    # global worst case caused a 2**9-scale margin blowup applied to every
    # chunk regardless of what it actually needed. Memoized per nd since the
    # same handful of tags recur across many strips.
    # ---- exact nd re-decision for margin sizing ---------------------------------
    # Must mirror the REAL dispatch decision (made later in the col-chunk loop)
    # bit-for-bit, including the USE_WEIGHTED_ND_DECISION branch -- an earlier
    # version of this helper only replicated the non-weighted _best_nd_tag(sr, ppc)
    # formula and fell back to the (possibly-disagreeing) planning-time hint
    # whenever USE_WEIGHTED_ND_DECISION was on. Since that flag defaults to True,
    # the "fix" was a silent no-op in the default config: margin sizing kept using
    # the stale hint while the real dispatch used the weighted formula, so a
    # chunk could be margin-sized for nd=0 (small reach) but actually dispatched
    # to nd=-1 (needs a wider reach), causing severe undercounts. This replicates
    # the exact weighted computation (_sparse_box_ppc_and_counts + total-area ppc
    # + share_occupied -> _best_nd_tag_weighted) so margin sizing and real
    # dispatch can never disagree.
    _exact_chunk_nd_cache = {}
    def _exact_chunk_nd(_ecr_lo, _ecr_hi, _ecc_lo, _ecc_hi):
        # Memoized: the main execution loop calls this TWICE with the exact
        # same (cr_lo, cr_hi, cc_lo, cc_hi) for every column-chunk -- once for
        # the strip-level margin sizing (_strip_nd_candidates), once again for
        # the chunk's own dispatch nd re-decision (_cc_nd_actual) -- doing the
        # same ~50-candidate weighted cost search twice for identical input.
        # Also helps disjoint-cluster workloads with many small, structurally-
        # similar chunks whose (cr_lo,cr_hi,cc_lo,cc_hi) repeats across a
        # session's chunk-count-heavy scenarios less directly, but the
        # guaranteed win is the same-strip double-call, always safe since the
        # key is an exact match, never an approximation.
        _e_key = (_ecr_lo, _ecr_hi, _ecc_lo, _ecc_hi)
        if _e_key in _exact_chunk_nd_cache:
            return _exact_chunk_nd_cache[_e_key]
        _e_result = _exact_chunk_nd_impl(_ecr_lo, _ecr_hi, _ecc_lo, _ecc_hi)
        _exact_chunk_nd_cache[_e_key] = _e_result
        return _e_result

    def _exact_chunk_nd_impl(_ecr_lo, _ecr_hi, _ecc_lo, _ecc_hi):
        _e_use_coherent = getattr(_cfg, 'USE_COHERENT_DENSITY_ESTIMATE', False)
        if _cfg.USE_WEIGHTED_ND_DECISION:
            _e_counts_fn = _sparse_box_ppc_and_counts_coherent if _e_use_coherent else _sparse_box_ppc_and_counts
            if _ecc_lo is not None:
                _e_reg_nz, _e_reg_cnt = _e_counts_fn(_ecr_lo, _ecr_hi, _ecc_lo // _K, _ecc_hi // _K)
                _e_col_span_coarse = (_ecc_hi // _K) - (_ecc_lo // _K) + 1
            else:
                _e_reg_nz, _e_reg_cnt = _e_counts_fn(_ecr_lo, _ecr_hi)
                _e_col_span_coarse = _max_cc_coarse
            _e_row_span_coarse = _ecr_hi - _ecr_lo + 1
            _e_total_area_fine = max(1, _e_row_span_coarse * _e_col_span_coarse * _K * _K)
            _e_total_pts_reg = float(_e_reg_cnt.sum()) if len(_e_reg_cnt) else 0.0
            _e_total_area_ppc = (_e_total_pts_reg / _e_total_area_fine) * math.pi * _sr * _sr
            _e_n_blocks_possible = max(1, _e_row_span_coarse * _e_col_span_coarse)
            _e_share_occupied = len(_e_reg_nz) / _e_n_blocks_possible
            _e_result = _best_nd_tag_weighted(_sr, _e_reg_nz, _e_reg_cnt, _e_total_area_ppc,
                                          share_occupied=_e_share_occupied)[0]
            if getattr(_cfg, '_DEBUG_TRACE_EXACT_CHUNK_ND', False):
                print(f'EXACT_ND cr=[{_ecr_lo},{_ecr_hi}] cc=[{_ecc_lo},{_ecc_hi}] '
                      f'row_span_coarse={_e_row_span_coarse} col_span_coarse={_e_col_span_coarse} '
                      f'n_occupied_cells={len(_e_reg_nz)} n_blocks_possible={_e_n_blocks_possible} '
                      f'total_pts={_e_total_pts_reg:.0f} total_area_ppc={_e_total_area_ppc:.4f} '
                      f'share_occupied={_e_share_occupied:.6f} -> nd={_e_result}', flush=True)
            return _e_result
        else:
            _e_ppc_fn = _sparse_box_ppc_coherent if _e_use_coherent else _sparse_box_ppc
            if _ecc_lo is not None:
                _e_reg_nz = _e_ppc_fn(_ecr_lo, _ecr_hi, _ecc_lo // _K, _ecc_hi // _K)
            else:
                _e_reg_nz = _e_ppc_fn(_ecr_lo, _ecr_hi)
            if _e_use_coherent:
                _e_reg_nz = _e_reg_nz[0]  # coherent variant returns (values, is_coherent)
            _e_ppc_est = float(np.percentile(_e_reg_nz, _PPC_CHUNK_PERCENTILE)) if len(_e_reg_nz) > 0 else 1.0
            return _best_nd_tag(_sr, _e_ppc_est)[0]

    _margin_for_nd_cache = {}
    def _margin_for_nd(_nd):
        if _nd >= 0:
            return r_rows
        if _nd in _margin_for_nd_cache:
            return _margin_for_nd_cache[_nd]
        from aabpl.utils.cell_geometry import classify_disk_cells_by_level
        _k = -_nd
        _coarse_spacing = grid_spacing * (2 ** _k)
        _, _, _coarse_ovlpd, _ = classify_disk_cells_by_level(
            grid_spacing=_coarse_spacing, r=r, include_boundary=False, nest_depth=0,
        )
        _max_coarse_offset = 0
        if len(_coarse_ovlpd):
            _max_coarse_offset = int(max(
                max(abs(int(_c[0])), abs(int(_c[1]))) for _c in _coarse_ovlpd.tolist()
            ))
        _margin = max(r_rows, (_max_coarse_offset + 1) * (2 ** _k))
        _margin_for_nd_cache[_nd] = _margin
        return _margin

    # ---- super-cell (nd < 0) pre-computation -----------------------------------
    # Runs unconditionally so the production adaptive-nd path can use super-cells.
    # Lazily built (not eagerly for all depths): each level's contain+overlap parity
    # expansion is O(4^k) — building all 9 depths upfront regardless of whether
    # adaptive dispatch ever selects them is a severe, needless cost. Built on first
    # actual per-chunk use via _ensure_super_cell_nd, memoized per nd.
    _nd_super_shared_cntd  = {}  # nd -> list[4^k] of codec offset arrays (by parity)
    _nd_super_sr_ovlpd     = {}  # nd -> list[4^k] of codec offset arrays (by parity)
    _nd_super_sr_ovlpd_max = {}  # nd -> max len across parities
    from .nd_choice import (_BENCH as _nd_bench, _SR_VALS as _nd_sr_vals,
                             blended_cost_ms as _nd_blended_cost_ms)
    import numpy as _np_sr_key
    _sr_key_idx   = int(_np_sr_key.searchsorted(_nd_sr_vals, _sr, side='right')) - 1
    _sr_key_idx   = max(0, min(_sr_key_idx, len(_nd_sr_vals) - 1))
    _sr_bench_key = float(_nd_sr_vals[_sr_key_idx])  # closest benchmarked sr -- used for merge-decision time estimates too
    _bench_nd_min = min((tag[0] for tag in _nd_bench.get(_sr_bench_key, {})
                         if tag[0] < 0), default=0)

    def _ensure_super_cell_nd(_sc_nd):
        """Build (and cache) the contain+overlap parity templates for one super-cell
        depth, on first use. Cost is O(4^k) where k=-_sc_nd; only ever paid for
        depths adaptive dispatch actually selects for some chunk."""
        if _sc_nd in _nd_super_shared_cntd:
            return
        import numpy as _np_sc
        _k = -_sc_nd
        _coarse_spacing = grid_spacing * (2 ** _k)
        _ce = _build_lookups(None, grid_spacing=_coarse_spacing, r=r,
                             nest_depth=0, silent=True)

        def _expand_coarse(coarse_cells, pr, pc, _step=2**_k):
            """Expand coarse-grid offsets to fine-grid level-0 offsets."""
            if isinstance(coarse_cells, frozenset):
                _tuples = list(coarse_cells)
            else:
                _arr = _np_sc.array(coarse_cells, dtype=float)
                _tuples = [tuple(_arr[_i]) for _i in range(len(_arr))] if len(_arr) else []
            fine = set()
            for (_lv, _dr_c, _dc_c) in _tuples:
                for _i in range(_step):
                    for _j in range(_step):
                        fine.add((0, (int(_step * _dr_c + _i - pr),
                                     int(_step * _dc_c + _j - pc))))
            return frozenset(fine)

        _sc_cntd_parity  = []
        _sc_ovlpd_parity = []
        for _pr in range(2 ** _k):
            for _pc in range(2 ** _k):
                _fc = _expand_coarse(_ce['shared_cntd_cells'], _pr, _pc)
                _sc_cntd_parity.append(codec.offset_int(_fc))
                # Super-cells are single-region by construction (a coarse cell has
                # no per-fine-region breakdown) — needed independent of the
                # cfg.SINGLE_REGION global, since adaptive per-chunk nd selection
                # can dispatch a super-cell tag on any chunk regardless of that flag.
                _fo = _expand_coarse(
                    _ce.get('single_region_ovlpd_cells', frozenset()), _pr, _pc)
                _sc_ovlpd_parity.append(codec.offset_int(_fo))
        _nd_super_shared_cntd[_sc_nd]  = _sc_cntd_parity
        _nd_super_sr_ovlpd[_sc_nd]     = _sc_ovlpd_parity
        _nd_super_sr_ovlpd_max[_sc_nd] = max(len(a) for a in _sc_ovlpd_parity)

    def _ensure_deep_nd(_target_nd):
        """Extend adaptive-nd geometry beyond the upfront-chosen _nd_max on
        first use, mirroring _ensure_super_cell_nd's lazy-build pattern but
        for POSITIVE nd (deeper, not coarser). Replaces the old behaviour of
        silently clamping any chunk that wants nd > _nd_max down to _nd_max
        (a permanent, silent downgrade for that chunk -- measured this
        session via ND_CLAMP_STATS: up to 62.5% of chunks at r=3000 on real
        521k-point data). build_disk_region_lookups already checks
        disk_region_cache first (keyed on (r/grid_spacing, nest_depth,
        include_boundary)) before doing a full build, so a deeper level
        already built earlier in the same process (e.g. by a prior search
        call at the same r/spacing) is reused for free -- no separate cache
        check needed here. Downgrades fill in any intermediate levels
        between the old _nd_max and _target_nd, exactly like the startup
        loop above does for 0..nest_depth."""
        nonlocal _nd_max
        # config.DEEP_ND_MIN_BUILD/DEEP_ND_MAX_BUILD: bound the range this is
        # ever allowed to extend into. The build-vs-skip decision itself is
        # the amortization check at the call site, not this bound.
        _target_nd = max(getattr(_cfg, 'DEEP_ND_MIN_BUILD', 0),
                          min(_target_nd, getattr(_cfg, 'DEEP_ND_MAX_BUILD', 8)))
        if _target_nd <= _nd_max:
            return
        _new_top = _build_lookups(None, grid_spacing=grid_spacing, r=r,
                                   nest_depth=_target_nd, silent=True)
        _adaptive_nd_entries[_target_nd] = _new_top
        # Fill AND cache every intermediate level between the old _nd_max and
        # the new target via the same _downgrade_and_cache helper the
        # upfront 0..nest_depth loop uses -- not just the target depth
        # itself. See that helper's comment for why the cache-write matters.
        for _nd in range(_target_nd - 1, _nd_max, -1):
            _downgrade_and_cache(_nd, _nd + 1)
        for _nd in range(_nd_max + 1, _target_nd + 1):
            _e = _adaptive_nd_entries[_nd]
            _nd_shared_cntd[_nd]  = codec.offset_int(_e['shared_cntd_cells'])
            _nd_cntd_by_reg[_nd]  = {rid: codec.offset_int(cells)
                                     for rid, cells in _e['region_and_trgl_id_to_distinct_cntd_cells'].items()}
            _nd_ovlpd_by_reg[_nd] = {rid: codec.offset_int(cells)
                                     for rid, cells in _e['region_and_trgl_id_to_distinct_ovlpd_cells'].items()}
            _sr_arr = _e.get('single_region_ovlpd_cells')
            if _sr_arr is not None and len(_sr_arr):
                _nd_sr_ovlpd[_nd]   = codec.offset_int(_sr_arr)
            else:
                _nd_sr_ovlpd[_nd]   = single_region_ovlpd_offset
            _nd_sr_ovlpd_n[_nd] = len(_nd_sr_ovlpd[_nd])
        _nd_max = _target_nd

    if _TEST_RANDOM_ND:
        # Super-cell templates build lazily now (first per-chunk use, not here),
        # so there's nothing to report about them pre-loop — the old "N parities
        # per nd" printout no longer applies (the coarse-batch fast path has no
        # per-position parity concept at all; the old parity-template path only
        # still runs when _do_weight is set, and even then only per nd actually
        # dispatched, not eagerly for every nd in _nd_range_test).
        progress_print(f"[adaptive-nd] test range: nd={_nd_range_test}; "
                       f"sr_ovlpd cells per nd: "
                       f"{ {nd: (_nd_sr_ovlpd_n.get(nd, _nd_super_sr_ovlpd_max.get(nd, 'n/a'))) for nd in _nd_range_test} }")

    # ---- super-cell fast path: position-independent coarse-cell batching --------
    # For nd<0, coarse-cell size (grid_spacing * 2^k) grows exponentially with k
    # while r stays fixed, so r/coarse_spacing -> 0 for deep nd: essentially no
    # neighbour cell is ever "fully contained" (a contained cell must be entirely
    # within r, but the cell itself is bigger than r), and fine sub-position within
    # a coarse cell barely shifts which coarse neighbours could matter. So instead
    # of the old per-position (`_par`, up to 4^k variants) contain/overlap templates,
    # every point sharing a coarse home cell is batched together and checked against
    # a single fixed-size, position-independent neighbour block, sized generously
    # enough to cover the disk from ANY position within the home cell. Correctness
    # is unaffected by how loose that block is — the final distance check
    # (dx^2+dy^2<=r^2) is always exact; a looser block only means more (cheap,
    # since nd<0 is only ever chosen for sparse regions) candidates get checked.
    _super_tgt_index = {}  # nd -> dict(width, unique_keys, starts, ends, xy, vals) or None

    def _ensure_super_cell_target_index(_sc_nd):
        """Build (once, cached) the coarse-block candidate index for supercell nd.

        Restricted to the union of chunk regions that the pre-scan (see
        _nd_chunk_regions, built right after _strip_plans) found actually use
        this nd, dilated by _super_block_radius(nd) coarse blocks -- same margin
        _super_gather_candidates itself uses, so no true candidate is ever
        excluded. Previously this always indexed the FULL target grid the first
        time any chunk anywhere used a given nd, even if that nd's chunks only
        covered a small corner -- e.g. one nd=-6 chunk in an otherwise nd=0
        521k-point grid still paid for a full 521k-row index. _nd_chunk_regions
        being empty (random-nd testing mode, or an nd never seen in the
        pre-scan) falls back to the old full-grid behaviour, which is always
        safe, just not minimal.
        """
        if _sc_nd in _super_tgt_index:
            return
        if n_tgt == 0:
            _super_tgt_index[_sc_nd] = None
            return
        _k = -_sc_nd
        _regions = _nd_chunk_regions.get(_sc_nd)
        if _regions:
            _bR = _super_block_radius(_sc_nd)
            _dilate_fine = _bR * (2 ** _k)
            _mask = np.zeros(n_tgt, dtype=bool)
            for (_rlo, _rhi, _clo, _chi) in _regions:
                _mask |= ((tgt_rows_full >= _rlo - _dilate_fine) & (tgt_rows_full <= _rhi + _dilate_fine) &
                          (tgt_cols_full >= _clo - _dilate_fine) & (tgt_cols_full <= _chi + _dilate_fine))
            _sel_idx = np.flatnonzero(_mask)
            if len(_sel_idx) == 0:
                _super_tgt_index[_sc_nd] = None
                return
            _tcr = (tgt_rows_full[_sel_idx] >> _k).astype(np.int64)
            _tcc = (tgt_cols_full[_sel_idx] >> _k).astype(np.int64)
            _src_pts_full = pts_vals_xy_full[_sel_idx]
        else:
            _tcr = (tgt_rows_full >> _k).astype(np.int64)
            _tcc = (tgt_cols_full >> _k).astype(np.int64)
            _src_pts_full = pts_vals_xy_full
        _width = int(_tcc.max()) + 1
        _coarse_key = _tcr * _width + _tcc
        _order = np.argsort(_coarse_key, kind='stable')
        _cks = _coarse_key[_order]
        _bdry = np.empty(len(_cks), dtype=bool)
        _bdry[0] = True
        _bdry[1:] = _cks[1:] != _cks[:-1]
        _starts = np.flatnonzero(_bdry)
        _ends = np.empty_like(_starts)
        _ends[:-1] = _starts[1:]
        _ends[-1] = len(_cks)
        _super_tgt_index[_sc_nd] = dict(
            width=_width,
            unique_keys=_cks[_starts],
            starts=_starts, ends=_ends,
            xy=_src_pts_full[_order][:, -2:],
            vals=_src_pts_full[_order][:, :-2].astype(float),
        )

    def _super_block_radius(_sc_nd):
        """Supercells beyond home needed in each direction to guarantee no
        false negative: worst case a point sits at the very edge of its home
        supercell, so any point within radius r of it can be at most
        ceil(r/coarse_spacing) supercells further out. This is already tight
        -- the exact per-point distance check downstream (dx^2+dy^2<=r^2)
        is what actually filters candidates, so this only needs to guarantee
        no under-inclusion, not tightness beyond that.

        Previously had a flat "+1" on top of this, which meant every
        supercell depth searched at least a 5x5 block even once
        coarse_spacing >> r made a 3x3 (or smaller, for shallower sr) block
        already sufficient -- e.g. at sr=2.0, ceil(r/coarse_spacing) is
        already 1 (3x3) starting at nd=-1 and stays there for every deeper
        nd, so the "+1" was pure excess at every depth, worst at deep nd
        where it roughly triples the candidates gathered per group (see
        bench_overlap_micro.py profiling: avg candidates/group grew toward
        ~29% of n_tgt at nd=-10 before this fix).
        """
        _coarse_spacing = grid_spacing * (2 ** (-_sc_nd))
        return max(1, int(math.ceil(r / _coarse_spacing)))

    def _super_gather_candidates(_sc_nd, cr, cc, block_radius):
        """All target points within [cr-block_radius,cr+block_radius] x
        [cc-block_radius,cc+block_radius] coarse cells, concatenated."""
        idx = _super_tgt_index.get(_sc_nd)
        if idx is None:
            return np.empty((0, 2)), np.empty((0, n_c))
        _width = idx['width']
        _uk = idx['unique_keys']
        key_lo = (cr - block_radius) * _width
        key_hi = (cr + block_radius + 1) * _width
        i0 = int(np.searchsorted(_uk, key_lo, side='left'))
        i1 = int(np.searchsorted(_uk, key_hi, side='left'))
        if i0 >= i1:
            return np.empty((0, 2)), np.empty((0, n_c))
        _cc_vals = _uk[i0:i1] % _width
        _m = (_cc_vals >= cc - block_radius) & (_cc_vals <= cc + block_radius)
        if not _m.any():
            return np.empty((0, 2)), np.empty((0, n_c))
        _sel = np.flatnonzero(_m) + i0
        # Vectorised "concatenate multiple ranges" gather, mirroring the same
        # fix applied to gather_overlap_pts: one combined index array covering
        # all matched (start, end) ranges at once, then a single fancy-index
        # gather -- replaces what used to be a Python list comprehension
        # building one slice per matched supercell key + np.concatenate.
        _sel_starts = idx['starts'][_sel]
        _lens = idx['ends'][_sel] - _sel_starts
        _total = int(_lens.sum())
        if _total == 0:
            return np.empty((0, 2)), np.empty((0, n_c))
        _seg_offsets = np.cumsum(_lens) - _lens
        _combined_idx = (np.repeat(_sel_starts, _lens)
                          + np.arange(_total) - np.repeat(_seg_offsets, _lens))
        return idx['xy'][_combined_idx], idx['vals'][_combined_idx]

    def _process_super_cell_chunk(_sc_nd, sp_idx):
        """Batched, position-independent contain+overlap for a super-cell chunk.
        sp_idx: global indices (into rows/cols/point_xy/sums_within_disks) of the
        source points to process, already row/col-chunk-filtered."""
        nonlocal _n_super_grps, _n_super_candidates, _t_super_index_build, _t_super_loop
        nonlocal _t_super_gather, _t_super_distcheck
        if len(sp_idx) == 0:
            return
        _tib0 = _pt()
        _ensure_super_cell_target_index(_sc_nd)
        _t_super_index_build += _pt() - _tib0
        _tlp0 = _pt()
        _k  = -_sc_nd
        _bR = _super_block_radius(_sc_nd)
        _csr = rows[sp_idx] >> _k
        _csc = cols[sp_idx] >> _k
        _coarse_key = _csr.astype(np.int64) * 1_000_000_007 + _csc.astype(np.int64)
        _order = np.argsort(_coarse_key, kind='stable')
        _sp_sorted = sp_idx[_order]
        _cks = _coarse_key[_order]
        _bdry = np.empty(len(_cks), dtype=bool)
        _bdry[0] = True
        _bdry[1:] = _cks[1:] != _cks[:-1]
        _starts = np.flatnonzero(_bdry)
        _ends = np.empty_like(_starts)
        _ends[:-1] = _starts[1:]
        _ends[-1] = len(_cks)
        for _gi in range(len(_starts)):
            _g_idx = _sp_sorted[_starts[_gi]:_ends[_gi]]
            _cr = int(rows[_g_idx[0]] >> _k)
            _cc = int(cols[_g_idx[0]] >> _k)
            _tgg0 = _pt()
            _cand_xy, _cand_vals = _super_gather_candidates(_sc_nd, _cr, _cc, _bR)
            _t_super_gather += _pt() - _tgg0
            _n_super_grps += 1
            _n_super_candidates += len(_cand_xy)
            if len(_cand_xy) == 0:
                continue
            _tgd0 = _pt()
            _pxy = point_xy[_g_idx]
            sums_within_disks[_g_idx] += _batched_disk_sum(_pxy, _cand_xy, _cand_vals, r2)
            _t_super_distcheck += _pt() - _tgd0
        _t_super_loop += _pt() - _tlp0

    # ---- quadtree descent: build sub-cell entries in block_cell_slices ----------
    # Mirrors nest_next_lvl from aggregate_point_data_to_cells. Adds level-1..nd
    # entries keyed by absolute codec int so gather_overlap_pts can retrieve only
    # the sub-cell slices belonging to the overlap sub-cells of a boundary cell.
    def _nest_block_slices(blk_scnr, blk_rows, blk_cols, blk_n, block_cell_slices,
                           depth=None):
        """Add sub-cell (level 1..depth) entries to block_cell_slices via quadtree descent.

        depth defaults to nest_depth (native).  Pass a smaller value for adaptive-nd
        test to stop subdivision early; the scnr values are still encoded at the native
        depth so the bit-width is always correct for the stride formula.
        """
        if depth is None:
            depth = nest_depth
        if blk_n == 0 or depth == 0:
            return
        cell_bdry   = np.concatenate([[True],
                                      (blk_rows[1:] != blk_rows[:-1]) |
                                      (blk_cols[1:] != blk_cols[:-1])])
        cell_starts = _np_flatnonzero(cell_bdry)
        cell_ends   = _np_append(cell_starts[1:], blk_n)
        for cell_start, cell_end in zip(cell_starts.tolist(), cell_ends.tolist()):
            row0 = int(blk_rows[cell_start]); col0 = int(blk_cols[cell_start])
            stack = [(cell_start, cell_end, 0, 1, row0 - 0.5, col0 - 0.5)]
            while stack:
                pos_min, pos_max, subcell_base, lvl, sub_row, sub_col = stack.pop()
                # stride always uses nest_depth (scnr encoding) not chunk depth
                subcell_stride = 2**((nest_depth - lvl) * 2)
                quad_nrs = (blk_scnr[pos_min:pos_max] - subcell_base) // subcell_stride
                cur_pos = pos_min
                for qnr in range(4):
                    if cur_pos >= pos_max: break
                    rel_off = cur_pos - pos_min
                    if quad_nrs[rel_off] > qnr: continue
                    seg_len = int(np.searchsorted(quad_nrs[rel_off:], qnr + 1, side='left'))
                    next_pos        = cur_pos + seg_len
                    next_subcell_base = subcell_base + qnr * subcell_stride
                    child_row       = sub_row + qnr // 2 / (2**lvl)
                    child_col       = sub_col + qnr %  2 / (2**lvl)
                    cell_key        = int(codec.key(lvl,
                                                    child_row + 2**-(lvl + 1),
                                                    child_col + 2**-(lvl + 1)))
                    block_cell_slices[cell_key] = (cur_pos, next_pos)
                    if lvl + 1 <= depth:
                        stack.append((cur_pos, next_pos, next_subcell_base,
                                      lvl + 1, child_row, child_col))
                    cur_pos = next_pos

    # ---- chunk sizing via L2 budget ---------------------------------------------
    # Cost per target pt in the block: (n_c+2)*8 bytes (pts_vals_xy slice)
    # Cost per cell aggregate: CELL_DICT_BYTES_PER_ENTRY (see below)
    # We want  block_pts * pt_bytes + block_cells * cell_bytes  <= 0.9 * L2
    #
    # Estimate block_cells ~ block_pts * (avg cells per pt)
    # but we bound it simply: sort pts_per_row descending, cumsum until budget.
    pt_bytes   = (n_c + 2 + int(_do_weight)) * 8
    # block_cell_slices is a Python dict of int -> (start, end) tuple, not a
    # flat array -- real per-entry cost (measured via sys.getsizeof across
    # dict + boxed int keys + tuple values, temp/track_l3_memory.py) is
    # ~131 bytes, not the max(n_c,1)*8=8 bytes a flat-array model would
    # assume. Independent of n_c: the dict tracks (start,end) index pairs
    # only, never the n_c value columns themselves. Confirmed via regression
    # against measured real chunk memory across 15k/521k runs (R^2=0.994).
    # Was previously uncounted at its true size, causing chunks (especially
    # merged ones) to silently exceed the real L3 budget while this formula
    # still reported them as fitting.
    CELL_DICT_BYTES_PER_ENTRY = 131
    cell_bytes = CELL_DICT_BYTES_PER_ENTRY
    # Source-point bytes touched per point during the chunk's contain/overlap
    # pass (point_xy[start:end] -- a view, not a new allocation, but the
    # bytes still occupy cache lines while being read, so they count toward
    # L3 pressure the same as an allocation would). x,y as float64 = 16
    # bytes/point. Previously not counted in the row-strip sizing formula at
    # all (only used, via the wrong pt_bytes constant, in the column-split
    # memory check below).
    SRC_BYTES_PER_PT = 16

    # candidate buffer: hot in overlap pass, subtract from budget
    max_cells_per_region  = max(
        max((len(cells) for cells in ovlpd_cells_by_cell_region.values()), default=1),
        _sr_ovlpd_n_cells)
    # Per-level-0-cell target point counts, computed directly from raw target
    # points instead of cell_count_iter(grid) (which reads Layer 1's aggregated
    # id_to_vals_xy_by_lvl). This only ever needed point COUNTS per cell, never
    # the aggregated sums themselves, so it doesn't actually need aggregation
    # to have run -- confirmed by the AttributeError this raised when planning
    # was moved to run before aggregation. A direct bincount of tgt_rows_full/
    # tgt_cols_full (already available, already sorted by row) gives the same
    # per-cell counts without that dependency.
    if n_tgt:
        _pop_width = int(tgt_cols_full.max()) + 1
        _pop_key = tgt_rows_full.astype(np.int64) * _pop_width + tgt_cols_full.astype(np.int64)
        _pop_key_sorted = np.sort(_pop_key)
        _pop_bdry = _np_flatnonzero(np.concatenate([[True], _pop_key_sorted[1:] != _pop_key_sorted[:-1]]))
        _pop_counts = _np_append(_pop_bdry[1:], n_tgt) - _pop_bdry
        sorted_cell_pops = sorted(_pop_counts.tolist())
    else:
        sorted_cell_pops = []
    max_cands        = sum(sorted_cell_pops[-max_cells_per_region:]) if sorted_cell_pops else 1
    # candidate buffer is reused across overlap groups, not simultaneously hot with the
    # target block — don't subtract it from the block-sizing budget.
    l3_budget    = int(0.9 * _cfg.L3_BYTES)

    # pts_per_row from the sorted tgt array — O(n_unique_rows), one diff
    tgt_row_bdry  = _np_flatnonzero(_np_ones(n_tgt, dtype=bool)
                                    if n_tgt == 0 else
                                    np.concatenate([[True], tgt_rows_full[1:] != tgt_rows_full[:-1]]))
    pts_per_row   = _np_append(tgt_row_bdry[1:], n_tgt) - tgt_row_bdry  # length = n_unique_rows

    # unique (row,col) cells per unique row — needed for chunk cost estimate
    tgt_cell_bdry = _np_flatnonzero(
        np.concatenate([[True],
                        (tgt_rows_full[1:] != tgt_rows_full[:-1]) |
                        (tgt_cols_full[1:] != tgt_cols_full[:-1])]))
    # each row boundary is also a cell boundary; use searchsorted to count cells per row
    row_cell_pos   = _np_searchsorted(tgt_cell_bdry, _np_append(tgt_row_bdry, n_tgt), side='left')
    cells_per_row  = np.diff(row_cell_pos)   # shape (n_unique_rows,)

    # Source-point count per target row, estimated by scaling each row's
    # target-point share by the global n_src/n_tgt ratio -- cheap proxy for
    # a real per-row source binning, assumes source density is roughly
    # proportional to target density row-by-row (exact for uniform data,
    # approximate otherwise; still far better than the previous 0 estimate).
    src_per_row_est = pts_per_row * (n_pts / max(1, n_tgt))

    # joint cost per row, sort descending, cumsum
    row_cost       = (pts_per_row * pt_bytes + cells_per_row * cell_bytes
                       + src_per_row_est * SRC_BYTES_PER_PT)
    sorted_cost    = np.sort(row_cost)[::-1]
    # fixed: 2*r_rows overlap rows always in block regardless of chunk size
    fixed_cost     = int(np.sum(sorted_cost[:2 * r_rows]))
    remaining      = max(1, l3_budget - fixed_cost)
    if getattr(_cfg, '_DEBUG_DISJOINT_PHASE_TIMING', False):
        print(f'L3_BUDGET_DEBUG disjoint={getattr(_cfg, "USE_DISJOINT_COMPONENT_SCOPING", False)} '
              f'l3_budget={l3_budget} fixed_cost={fixed_cost} remaining={remaining} '
              f'r_rows={r_rows} n_tgt={n_tgt}', flush=True)
    cumcost        = np.cumsum(sorted_cost)
    fits           = int(np.searchsorted(cumcost, remaining, side='right'))
    _strip_fits    = max(1, fits)  # baseline strip height (rows that fit in L3)

    # Percentile for chunk nd dispatch: biases nd choice toward the denser
    # sub-region within a chunk footprint rather than the area-weighted mean.
    # Row zone-change detection uses row-max ppc (separate signal, not a percentile).
    _PPC_CHUNK_PERCENTILE = 75

    def _nd_mem_scale(tag):
        nd = tag[0]
        if getattr(_cfg, '_DEBUG_REVERT_MEM_SCALE_FIX', False):
            return float(2 ** max(0, nd)) if nd >= 0 else 0.5
        return float(2.0 ** nd)

    # Single source of truth for "should these two nd tags be treated as
    # the same mechanism" -- used by every zone/band/merge decision that
    # references it (row zone-noise damping, nd-heterogeneity col-banding,
    # etc). Both supercell tags (nd<0) count as "the same" mechanism by
    # default; USE_MAGNITUDE_AWARE_SUPERCELL_BANDING bounds how far apart
    # two supercell depths can be and still count as "the same" via
    # MAX_ND_GAP_FOR_LOOSE_SUPERCELL_MERGE (default 2) -- small gaps are
    # still damped as noise; large gaps are treated as a real difference.
    def _same_nd_mechanism(tag_a, tag_b):
        if _dbg_same_nd_counter is not None:
            _gid = _dbg_same_nd_current_group[0]
            _dbg_same_nd_counter[_gid] = _dbg_same_nd_counter.get(_gid, 0) + 1
        if tag_a[0] >= 0 or tag_b[0] >= 0:
            return tag_a == tag_b
        if not getattr(_cfg, 'USE_MAGNITUDE_AWARE_SUPERCELL_BANDING', False):
            return True  # any two supercell tags are "the same"
        return (tag_a[1] == tag_b[1]
                and abs(tag_a[0] - tag_b[0]) <= getattr(_cfg, 'MAX_ND_GAP_FOR_LOOSE_SUPERCELL_MERGE', 2))

    # DIAGNOSTIC ONLY (config._DEBUG_COUNT_SAME_ND_CALLS, default off):
    # per-group call-count tracking for _same_nd_mechanism, to check whether
    # calls concentrate in a handful of groups or spread evenly.
    # _dbg_same_nd_current_group is updated by _plan_chunks_clean's outer
    # loop at the start of each group's processing.
    _dbg_same_nd_counter = {} if getattr(_cfg, '_DEBUG_COUNT_SAME_ND_CALLS', False) else None
    _dbg_same_nd_current_group = [None]

    if getattr(_cfg, '_DEBUG_PER_CHUNK_CAPTURE', False):
        progress_print(
            f"Chunk-block: r_rows={r_rows}, chunk_rows={_strip_fits}, "
            f"col_split={'enabled' if ENABLE_COL_SPLIT else 'disabled'}, "
            f"tall_strips={'enabled' if ENABLE_TALL_STRIPS else 'disabled'}, "
            f"l3_budget={l3_budget//1024} KB, "
            f"~{(fixed_cost + int(np.sum(sorted_cost[:_strip_fits])))//1024} KB est. hot set")

    # ---- sort source points -----------------------------------------------------
    if suffix is None:
        suffix = '_' + str(r)
    sum_radius_names = [cn + suffix for cn in c]
    # Allocate writable numpy arrays for streaming output (pandas .values can be
    # read-only under COW; writing directly to these avoids that).
    _stream_out = {_cn: _np_zeros(n_pts) for _cn in sum_radius_names}
    pts_source[sum_radius_names] = 0

    # EXPERIMENTAL (config.USE_DISJOINT_COMPONENT_SCOPING, default off): label
    # each source point with its disjoint-cloud component id (union of source
    # + target occupancy -- see _find_disjoint_clouds docstring for why both
    # are needed) and use it as the PRIMARY sort key below. This piggybacks
    # on the snake-sort pts_source already pays for -- no separate reorder
    # pass. The strip-building loop further down forces a break whenever
    # component id changes, so a strip can never span a real gap between two
    # disjoint clouds. NOT validated in production yet.
    # ARCHITECTURE (2026-07): the separate non-disjoint code path is being
    # phased out -- every search now routes through the disjoint machinery
    # below, but with a cheap upfront fast-path: if the caller explicitly
    # disabled disjoint scoping (config.USE_DISJOINT_COMPONENT_SCOPING=False)
    # OR the whole dataset already fits within DISJOINT_MERGE_CAP_MULT x the
    # per-group L3 point cap (the same size threshold the merge-fold step
    # already uses to decide "small enough to not bother splitting further"),
    # skip _find_disjoint_clouds and the merge-fold construction entirely and
    # just assign every point component_group_id=0 (a single trivial group).
    # This is functionally equivalent to the old non-disjoint path (one
    # group = no cross-group isolation needed = same planning/execution
    # shape as before) while keeping only ONE code path to maintain. The old
    # `else:` non-disjoint branches further down are intentionally left in
    # place for now (gated off, unreachable via _use_disjoint_scoping now
    # always being True) rather than deleted immediately -- planned removal
    # once this fast-path is validated across the existing regression/
    # mismatch/sweep suite.
    _disjoint_requested = getattr(_cfg, 'USE_DISJOINT_COMPONENT_SCOPING', False)
    _use_disjoint_scoping = True
    _cgrp_cap_pts_upfront = max(1, int(l3_budget / pt_bytes))
    # Skip disjoint-cluster detection entirely if the WHOLE dataset already
    # fits within DISJOINT_MERGE_CAP_MULT x the per-group L3 point cap --
    # deliberately the bare cap_pts x mult (NOT the smaller 0.05-scaled
    # _cgrp_small_thresh used below for folding individual small RAW
    # clusters together -- that 5% figure calibrates how aggressively small
    # clusters get merged with NEIGHBORS post-detection, a different
    # decision from "is detection worth running on the whole dataset at
    # all").
    _skip_disjoint_detect = (
        not _disjoint_requested
        or n_pts <= getattr(_cfg, 'DISJOINT_MERGE_CAP_MULT', 1.0) * _cgrp_cap_pts_upfront
    )
    # Real (non-duplicated, non-simulated) phase timing, gated behind
    # config._DEBUG_DISJOINT_PHASE_TIMING -- unlike _DEBUG_REPORT_DISJOINT_
    # CLUSTERS (which re-runs _find_disjoint_clouds a SECOND time purely for
    # reporting and simulates its own bin-packing), this times the ACTUAL
    # detection/merge/sort/planning/execution phases the real run makes, and
    # reports the ACTUAL n_raw_clusters/n_merged_groups the real _cgrp_map
    # produced. Deliberately NOT gated on _use_disjoint_scoping: printing
    # 'setup'/'planning'/'execution' for the non-disjoint path too (skipping
    # only the disjoint-specific detect/merge/sort lines, which genuinely
    # don't exist there) makes the two paths' phase breakdowns directly
    # comparable -- confirmed this session that computing a non-disjoint
    # 'execution' bucket as (total - planning) instead double-counted the
    # pre-detect setup phase into it, breaking the diff-table arithmetic
    # (component diffs didn't sum to total_diff). 'setup' now covers
    # function entry through the point immediately before detect (or,
    # for non-disjoint, through the equivalent point right before the sort
    # call) on BOTH paths, so every phase is mutually exclusive and
    # exhaustive for both, and diffs sum cleanly to the total diff.
    _dbg_phase = getattr(_cfg, '_DEBUG_DISJOINT_PHASE_TIMING', False)
    if _dbg_phase:
        print(f'DISJOINT_PHASE setup: {(time.process_time()-_t_func_start)*1000:.2f}ms', flush=True)
    _phase_t0 = time.process_time() if _dbg_phase else None
    if _use_disjoint_scoping:
        if _skip_disjoint_detect:
            # Fast path: skip _find_disjoint_clouds + merge-fold entirely, treat
            # the whole dataset as ONE trivial group. Reconstructs exactly the
            # end-state the detect+merge-fold block below would produce for a
            # single-group outcome (same variable names, same dtypes), so every
            # downstream consumer of these variables works unmodified.
            _src_component_id_pre = np.zeros(n_pts, dtype=np.int64)
            _tgt_component_id = np.zeros(len(pts_vals_xy_full), dtype=np.int64)
            _uniq_comp = np.zeros(1, dtype=np.int64)
            _comp_counts = np.array([n_pts], dtype=np.int64)
            _cgrp_next = 1
            # Deliberately NOT written to pts_source (unlike the full-detect
            # path): every point has the same id (0) regardless of row
            # order, so there's nothing for the upcoming sort to preserve --
            # skip the write-sort-readback-drop round trip through the
            # DataFrame entirely and just construct the (constant) arrays
            # directly, after sorting, from n_pts alone. See _sort_keys_prefix
            # and the component_id/component_group_id construction below.
            _tgt_component_group_id = _tgt_component_id
            if _dbg_phase:
                print(f'DISJOINT_PHASE detect(_find_disjoint_clouds): 0.00ms (skipped, fast path)', flush=True)
                _phase_t0 = time.process_time()
        else:
            _combined_x = np.concatenate([pts_source[x].values, pts_vals_xy_full[:, -2]])
            _combined_y = np.concatenate([pts_source[y].values, pts_vals_xy_full[:, -1]])
            _comp_labels_all = _find_disjoint_clouds(_combined_x, _combined_y, r)
            if _dbg_phase:
                print(f'DISJOINT_PHASE detect(_find_disjoint_clouds): {(time.process_time()-_phase_t0)*1000:.2f}ms', flush=True)
                _phase_t0 = time.process_time()
            _src_component_id_pre = _comp_labels_all[:n_pts]
            # TARGET points' component labels, from the SAME combined union-find
            # used for source -- pts_vals_xy_full/tgt_rows_full/tgt_cols_full are
            # already sorted by this point, so _tgt_component_id directly aligns
            # with that order. Used to scope the clean planner's OWN density
            # lookups (_row_profile_cluster etc.) to just the current source
            # cluster's own target data.
            _tgt_component_id = _comp_labels_all[n_pts:]

            # Merge small clusters into GROUPS (same greedy, sort-ascending,
            # 5%-of-L3-cap-threshold logic as the diagnostic report) -- this is
            # the actual fix for planning/execution running once per RAW cluster
            # (thousands, for many-tiny-cluster datasets): the merged group,
            # not the raw cluster, becomes the PRIMARY sort/break unit below, so
            # the row-scan's outer iteration count matches merged-group count
            # (~dozen) instead of raw cluster count. Raw component_id is kept
            # and used internally (forcing the exact _fit_penalty check at a
            # raw sub-cluster transition, and for cluster-scoped density lookups)
            # so merging groups together for iteration-count purposes never
            # blends density across an actual disjoint gap -- only the transition
            # discovery mechanism gets coarser, not the correctness guarantees.
            _uniq_comp, _comp_inv, _comp_counts = np.unique(
                _src_component_id_pre, return_inverse=True, return_counts=True)
            _cgrp_cap_pts = max(1, int(l3_budget / pt_bytes))
            _cgrp_small_thresh = (0.05 * getattr(_cfg, 'DISJOINT_MERGE_CAP_MULT', 1.0)) * _cgrp_cap_pts
            # Sized from the max of BOTH source and target labels (not source
            # alone) -- a target-only cluster (no nearby source points at all)
            # would have a label absent from _uniq_comp; sizing from source
            # labels only risks an index error, or worse, a silent wrong
            # default-to-group-0 for an unrelated target-only region. Any label
            # not assigned below (source-side loop only visits source labels)
            # gets its own singleton merged-group id, appended after all the
            # source-derived groups -- never silently defaults to 0.
            _cgrp_map_size = max(int(_uniq_comp.max()), int(_tgt_component_id.max()) if len(_tgt_component_id) else 0) + 1
            _cgrp_map = np.full(_cgrp_map_size, -1, dtype=np.int64)
            # Merge-fold by SPATIAL PROXIMITY (centroid distance), not by size
            # rank: the earlier ascending-size fold could stitch together two
            # clusters purely because they happened to be similarly small, even
            # if they were on opposite sides of the dataset -- confirmed this
            # session (10-tile plot) that this created merged groups spanning
            # every tile at once, ballooning each group's column-chunk bounding
            # box to the full dataset width. Instead: a small cluster (below the
            # 5% threshold) merges into whichever OTHER cluster is nearest by
            # centroid distance, regardless of that neighbor's own size --
            # absorbing a small cluster into an already-large nearby one costs
            # almost nothing (it needed its own strip anyway) and keeps the
            # group spatially compact, unlike forcing two distant small clusters
            # together just to satisfy a same-size pairing.
            # Simplified from an earlier 2D-centroid-distance KD-tree/union-find
            # version (reverted this session: it degraded badly on datasets with
            # tens of thousands of raw clusters -- 10-tile r=100's 23,339
            # clusters took 37s in this one step alone, since re-querying the
            # KD-tree with an expanding k per cluster gets progressively more
            # expensive as neighbors get absorbed into shared roots). Sorting by
            # mean x and greedily folding CONSECUTIVE-IN-X clusters together
            # (same O(n log n) sort + O(n) scan shape as the original size-only
            # fold, just keyed by position instead of size) gets most of the
            # same spatial-compactness benefit at a fraction of the cost -- for
            # this codebase's tiled/columnar data in particular, x-position is
            # the dominant axis of spatial separation between clusters anyway.
            _cent_x = np.zeros(len(_uniq_comp), dtype=np.float64)
            np.add.at(_cent_x, _comp_inv, pts_source[x].values)
            _cent_x /= _comp_counts
            _cgrp_x_order = np.argsort(_cent_x)
            _cgrp_sorted_ids = _uniq_comp[_cgrp_x_order]
            _cgrp_sorted_sizes = _comp_counts[_cgrp_x_order]
            # Guard (bug found this session): the inner absorb-loop must NOT
            # pull in a next-in-x-order cluster that's already >= threshold on
            # its own -- confirmed via a 30-tile r=750 trace that a 12,034-point
            # dominant cluster got swallowed into an accumulating run of tiny
            # stragglers purely because of where it fell in x-order, producing
            # a 267-raw-cluster merged group spanning 2+ tile-widths (the exact
            # "scattered mega-group" pathology this fold was written to avoid,
            # just reconstructed via x-order instead of size-order). An
            # already-large candidate closes off whatever's accumulated so far
            # (even if still under threshold) and starts its own group.
            _cgrp_i, _cgrp_n, _cgrp_next = 0, len(_cgrp_sorted_sizes), 0
            while _cgrp_i < _cgrp_n:
                _cgrp_cur = int(_cgrp_sorted_sizes[_cgrp_i])
                _cgrp_map[int(_cgrp_sorted_ids[_cgrp_i])] = _cgrp_next
                _cgrp_i += 1
                while (_cgrp_cur < _cgrp_small_thresh and _cgrp_i < _cgrp_n
                       and int(_cgrp_sorted_sizes[_cgrp_i]) < _cgrp_small_thresh):
                    _cgrp_cur += int(_cgrp_sorted_sizes[_cgrp_i])
                    _cgrp_map[int(_cgrp_sorted_ids[_cgrp_i])] = _cgrp_next
                    _cgrp_i += 1
                _cgrp_next += 1
            # Any remaining -1 entries are labels never seen among source points
            # (target-only clusters) -- give each its own singleton group.
            _cgrp_unmapped = np.flatnonzero(_cgrp_map == -1)
            for _u in _cgrp_unmapped.tolist():
                _cgrp_map[_u] = _cgrp_next
                _cgrp_next += 1
            pts_source['_component_id']   = _src_component_id_pre
            pts_source['_component_group_id'] = _cgrp_map[_src_component_id_pre]
            # component_group_id (NOT raw component_id) is the primary sort key
            # and the row-scan's hard-break unit. Design (corrected from an
            # earlier broken attempt this session): detect clusters, group them,
            # then run the SAME unmodified row+column planner independently per
            # group -- exactly like the normal (non-disjoint) path treats the
            # whole dataset. No special "force exact check" or raw-cluster hard
            # break needed: _fit_penalty/_band_from_profile ALREADY correctly
            # handle "different regions have different densities" (that's
            # column-banding's entire purpose), including "different clusters
            # occupy different columns" -- there was never a need for extra
            # disjoint-specific protection inside the row-scan itself. Sorting
            # by component_group_id ALONE (no raw component_id in between)
            # keeps row values monotonically increasing WITHIN each group
            # (critical: a secondary raw-component_id sort key breaks this, since
            # a different raw cluster's rows don't continue from the previous
            # one's -- confirmed this session via a severe hang/near-infinite
            # loop when that was tried).
            _tgt_component_group_id = _cgrp_map[_tgt_component_id]

        # Group-sorted view of target points (config.USE_DISJOINT_COMPONENT_SCOPING):
        # one O(n log n) sort, done ONCE for the whole dataset (not per group),
        # so each group's own target points become a contiguous slice --
        # _tgt_grp_range[gid] = (start, end) into _tgt_rows_grp_sorted/
        # _tgt_cols_grp_sorted. Built here (before the per-group fixed-cost
        # loop just below) so that loop can slice directly instead of
        # rescanning the full target array with a boolean mask per group --
        # previously O(n_groups * n_tgt), now a straight partition.
        if n_tgt > 0 and _cgrp_next == 1:
            # Single-group fast path: with only one group, EVERY point is
            # already "sorted by group id" (the id is constant), so the
            # argsort below would compute the identity permutation at real
            # O(n log n) cost for nothing. Use the already row-sorted global
            # arrays directly -- no copy, no permutation.
            _tgt_rows_grp_sorted = tgt_rows_full
            _tgt_cols_grp_sorted = tgt_cols_full
            _tgt_grp_range = np.array([[0, n_tgt]], dtype=np.int64)
            _pts_vals_xy_grp_sorted = pts_vals_xy_full
            _tgt_scnr_grp_sorted = tgt_scnr_full
        elif n_tgt > 0:
            _tgt_grp_order = np.argsort(_tgt_component_group_id, kind='stable')
            _tgt_rows_grp_sorted = tgt_rows_full[_tgt_grp_order]
            _tgt_cols_grp_sorted = tgt_cols_full[_tgt_grp_order]
            _tgt_grp_sorted_ids = _tgt_component_group_id[_tgt_grp_order]
            _tgt_grp_bdry = np.flatnonzero(np.concatenate(
                [[True], _tgt_grp_sorted_ids[1:] != _tgt_grp_sorted_ids[:-1]]))
            _tgt_grp_bdry_ids = _tgt_grp_sorted_ids[_tgt_grp_bdry]
            _tgt_grp_ends = np.append(_tgt_grp_bdry[1:], n_tgt)
            _tgt_grp_range = np.full((_cgrp_next, 2), -1, dtype=np.int64)
            _tgt_grp_range[_tgt_grp_bdry_ids, 0] = _tgt_grp_bdry
            _tgt_grp_range[_tgt_grp_bdry_ids, 1] = _tgt_grp_ends
            # Same permutation applied to the value/scnr arrays -- needed so the
            # execution-phase per-strip gather (config.USE_DISJOINT_COMPONENT_SCOPING)
            # can read directly from a single group's contiguous slice instead of
            # gathering the GLOBAL row-band and masking by component_group_id
            # afterward (see the execution loop for the full rationale).
            _pts_vals_xy_grp_sorted = pts_vals_xy_full[_tgt_grp_order]
            _tgt_scnr_grp_sorted = tgt_scnr_full[_tgt_grp_order]
        else:
            _tgt_rows_grp_sorted = None
            _tgt_cols_grp_sorted = None
            _tgt_grp_range = None
            _pts_vals_xy_grp_sorted = None
            _tgt_scnr_grp_sorted = None

        # Sorting by a constant key changes nothing (stable sort), so the
        # fast (single-group) path skips it entirely -- also means the
        # _component_group_id column never needs to exist in pts_source at
        # all for that case (see the fast-path branch above and the
        # component_id/component_group_id construction after the sort below).
        _sort_keys_prefix = [] if _skip_disjoint_detect else ['_component_group_id']
        # Recompute the L3 "fixed cost" floor (originally set above, before
        # disjoint scoping existed) PER GROUP instead of blending every
        # group's most-expensive rows together globally. The original
        # computation sums the top 2*r_rows most-expensive TARGET ROWS
        # across the WHOLE dataset as the unavoidable per-strip "overlap
        # rows" overhead -- reasonable for one spatially-coherent dataset,
        # but once points are grouped into disjoint clusters, no single
        # strip will ever need to hold overlap rows from MULTIPLE different
        # groups at once (they're processed as separate strips by
        # construction). The true worst-case fixed cost is the max over any
        # ONE group's own top-(2*r_rows) rows, not the sum of the globally
        # most-expensive rows blended across every group's contribution to
        # a shared physical row (common when many disjoint clusters
        # coincidentally occupy the same row range, e.g. tiled synthetic
        # data). Confirmed this session: without this, a 30-tile stress
        # test collapsed `remaining` to its 1-byte floor (fixed_cost 9.16M
        # vs l3_budget 5.66M) -- forcing every atomic row into its own
        # strip. Non-disjoint mostly escaped the visible damage only
        # because it already collapses shared-row-space tiles into very
        # few atomic rows; disjoint mode's inherently larger (but
        # necessary, for per-group correctness) atomic-row count turned
        # the same underlying bug into a 1,410-strip explosion.
        if n_tgt > 0:
            _max_fixed_cost = 0
            for _gid in range(_cgrp_next):
                _g_lo, _g_hi = int(_tgt_grp_range[_gid, 0]), int(_tgt_grp_range[_gid, 1])
                _g_n_tgt = _g_hi - _g_lo
                if _g_n_tgt <= 0:
                    continue
                _g_tgt_rows = _tgt_rows_grp_sorted[_g_lo:_g_hi]
                _g_tgt_cols = _tgt_cols_grp_sorted[_g_lo:_g_hi]
                _g_row_bdry = _np_flatnonzero(np.concatenate(
                    [[True], _g_tgt_rows[1:] != _g_tgt_rows[:-1]]))
                _g_pts_per_row = _np_append(_g_row_bdry[1:], _g_n_tgt) - _g_row_bdry
                _g_cell_bdry = _np_flatnonzero(np.concatenate(
                    [[True], (_g_tgt_rows[1:] != _g_tgt_rows[:-1])
                             | (_g_tgt_cols[1:] != _g_tgt_cols[:-1])]))
                _g_row_cell_pos = _np_searchsorted(
                    _g_cell_bdry, _np_append(_g_row_bdry, _g_n_tgt), side='left')
                _g_cells_per_row = np.diff(_g_row_cell_pos)
                _g_src_per_row_est = _g_pts_per_row * (n_pts / max(1, n_tgt))
                _g_row_cost = (_g_pts_per_row * pt_bytes + _g_cells_per_row * cell_bytes
                               + _g_src_per_row_est * SRC_BYTES_PER_PT)
                _g_sorted_cost = np.sort(_g_row_cost)[::-1]
                _g_fixed_cost = int(np.sum(_g_sorted_cost[:2 * r_rows]))
                _max_fixed_cost = max(_max_fixed_cost, _g_fixed_cost)
            fixed_cost = _max_fixed_cost
            remaining = max(1, l3_budget - fixed_cost)
            if _dbg_phase:
                print(f'DISJOINT_PHASE L3 fixed_cost recompute (per-group max): '
                      f'fixed_cost={fixed_cost} remaining={remaining}', flush=True)
        if _dbg_phase:
            print(f'DISJOINT_PHASE merge-fold construction: {(time.process_time()-_phase_t0)*1000:.2f}ms '
                  f'n_raw_clusters={len(_uniq_comp)} n_merged_groups={_cgrp_next}', flush=True)
            _phase_t0 = time.process_time()
    else:
        _sort_keys_prefix = []
        _tgt_component_id = None
        _tgt_component_group_id = None
        _tgt_rows_grp_sorted = None
        _tgt_cols_grp_sorted = None
        _tgt_grp_range = None
        _pts_vals_xy_grp_sorted = None
        _tgt_scnr_grp_sorted = None

    # Snake sort: even rows L→R, odd rows R→L — keeps adjacent rows spatially
    # contiguous so the next chunk's leading edge is cache-warm.
    # Sort pts_source in-place (keeping the same object) so write-backs via
    # pts_source[col] = ... remain visible to the caller after we restore order.
    _snake_col = pts_source[col_name] * np.where(pts_source[row_name] % 2 == 0, 1, -1)
    pts_source['_snake_col']   = _snake_col
    pts_source['_orig_order']  = np.arange(n_pts)
    pts_source.sort_values(_sort_keys_prefix + [row_name, '_snake_col', 'region_and_trgl_id'], inplace=True)
    pts_source.drop(columns=['_snake_col'], inplace=True)
    if _skip_disjoint_detect:
        # Never written to pts_source (see the fast-path branch above) --
        # constant regardless of row order, so just build it directly at
        # the post-sort length instead of reading a column back out.
        component_id = np.zeros(n_pts, dtype=np.int64)
        component_group_id = component_id
    elif _use_disjoint_scoping:
        component_id    = pts_source['_component_id'].values.copy()
        component_group_id = pts_source['_component_group_id'].values.copy()
        pts_source.drop(columns=['_component_id', '_component_group_id'], inplace=True)
    else:
        component_id = None
        component_group_id = None
    if _dbg_phase:
        print(f'DISJOINT_PHASE sort (component_group_id + row + snake_col): {(time.process_time()-_phase_t0)*1000:.2f}ms', flush=True)
        _phase_t0 = time.process_time()
    point_xy        = pts_source[[x, y]].values
    point_offset    = pts_source[[off_x, off_y]].values if _do_weight else None
    rows            = pts_source[row_name].values.astype(np.int64)
    cols            = pts_source[col_name].values.astype(np.int64)
    cell_region     = pts_source[cell_region_name].values
    region_and_trgl = pts_source['region_and_trgl_id'].values
    # ---- exact super-cell classification (per unique source home cell / block) ----
    # For each unique home cell (or K×K block when block_k>1), build a super-cell
    # polygon covering all grid cells the disk can possibly reach, then intersect
    # with the study area once.  Per-point invalid_area is computed in the pre-loop
    # below using a fine-grained cell-ID trigger: only points whose disk touches a
    # non-fully-interior cell pay the Shapely disk.intersection(clip_poly) cost.
    # block_k>1 groups K×K home cells into one shared super-cell, reducing the
    # number of Shapely pre-computations by K² with no accuracy loss on the trigger.
    _do_exact_supercell = False
    if _do_weight and area_weight == 'exact':
        _sa_pc = getattr(grid, 'study_area', None)
        if _sa_pc is not None:
            try:
                try:
                    from shapely import unary_union as _shp_union
                except ImportError:
                    from shapely.ops import unary_union as _shp_union
                from shapely import (
                    box        as _shp_box_pc,
                    prepare    as _shp_prepare_pc,
                    contains   as _shp_cp_pc,
                    intersects as _shp_ix_pc,
                )
                from shapely.affinity import translate as _shp_translate_pc
                from shapely.geometry import Point as _ShpPoint
                _shp_prepare_pc(_sa_pc)
                _sp_pc = grid_spacing
                _x0_pc = grid._search_internals.bounds.xmin
                _y0_pc = grid._search_internals.bounds.ymin

                # Super-cell template relative to block origin (bottom-left of cell
                # (0,0) within the block).  For block_k=1 this is the standard
                # single-home-cell template; for block_k=K it expands to cover the
                # union of all K×K home cells' disk footprints.
                # A cell (dr, dc) is included when the rectangle-to-rectangle gap
                # between the K×K block [0, K*sp]² and the cell is ≤ r, i.e. ANY
                # point in the block can reach the cell.  This guarantees that for
                # every point p in a home cell within the block, disk(p) ⊆ super_cell,
                # so disk(p) ∩ study_area = disk(p) ∩ clip_poly exactly.
                # Gap formula: max(0, dc−K, −dc−1)·sp along each axis (rectangle gap).
                _K = _exact_block_k
                _sc_rel_boxes = []
                for _dr in range(-r_rows - 2, r_rows + _K + 2):
                    _dy = max(0.0, (_dr - _K) * _sp_pc, -(_dr + 1) * _sp_pc)
                    for _dc in range(-r_rows - 2, r_rows + _K + 2):
                        _dx = max(0.0, (_dc - _K) * _sp_pc, -(_dc + 1) * _sp_pc)
                        if math.sqrt(_dx * _dx + _dy * _dy) <= r:
                            _sc_rel_boxes.append(_shp_box_pc(
                                _dc * _sp_pc,       _dr * _sp_pc,
                                (_dc + 1) * _sp_pc, (_dr + 1) * _sp_pc,
                            ))
                _sc_tmpl = _shp_union(_sc_rel_boxes)   # computed once per rs() call

                # Classify each unique block (block_k=1 → each home cell is its own block).
                # Result: 'interior' | 'exterior' | clip_poly (= study_area ∩ super_cell).
                _unique_home_cells = set(zip(rows.tolist(), cols.tolist()))
                _unique_blocks = set((_hr // _K, _hc // _K) for _hr, _hc in _unique_home_cells)
                _block_clips = {}   # (block_r, block_c) -> 'interior'|'exterior'|clip_poly
                for _br, _bc in _unique_blocks:
                    _bx0 = _x0_pc + _bc * _K * _sp_pc
                    _by0 = _y0_pc + _br * _K * _sp_pc
                    _sc  = _shp_translate_pc(_sc_tmpl, xoff=_bx0, yoff=_by0)
                    if _shp_cp_pc(_sa_pc, _sc):
                        _block_clips[(_br, _bc)] = 'interior'
                    elif not _shp_ix_pc(_sa_pc, _sc):
                        _block_clips[(_br, _bc)] = 'exterior'
                    else:
                        _block_clips[(_br, _bc)] = _sa_pc.intersection(_sc)
                # Map each home cell to its block's clip result.
                _precise_home_cells = {
                    (_hr, _hc): _block_clips.get((_hr // _K, _hc // _K), 'interior')
                    for _hr, _hc in _unique_home_cells
                }
                _do_exact_supercell = True
            except ImportError:
                pass   # shapely unavailable → fall through to _invalid_keys path

    # group boundaries — compare (row, col) directly to avoid int64 overflow
    # that would occur with codec keys when row_stride * row_range overflows.
    cell_changed    = _np_ones(n_pts, dtype=bool)
    contain_changed = _np_ones(n_pts, dtype=bool)
    overlap_changed = _np_ones(n_pts, dtype=bool)
    if n_pts > 1:
        cell_changed[1:]    = (rows[1:] != rows[:-1]) | (cols[1:] != cols[:-1])
        if _single_region:
            # All points in the same cell share one template — only split at cell boundaries.
            contain_changed[1:] = cell_changed[1:]
            overlap_changed[1:] = cell_changed[1:]
        else:
            region_trgl_changed = region_and_trgl[1:] != region_and_trgl[:-1]
            contain_changed[1:] = (cell_changed[1:]
                                   | (cell_region[1:] // contain_region_mult != cell_region[:-1] // contain_region_mult)
                                   | region_trgl_changed)
            overlap_changed[1:] = (cell_changed[1:]
                                   | (cell_region[1:] % contain_region_mult != cell_region[:-1] % contain_region_mult)
                                   | region_trgl_changed)

    sums_within_disks = _np_zeros((n_pts, n_c))
    if _do_weight:
        invalid_area = _np_zeros(n_pts)

    # Sub-timers and counters (injected into func_timer_dict at end).
    from time import process_time as _pt, perf_counter as _wall_pt
    _t_contain      = 0.0
    _t_overlap      = 0.0
    _t_ov_gather    = 0.0   # gather_overlap_pts sub-step
    _t_ov_distcheck = 0.0   # distance-check matrix sub-step
    _n_contain_grps = 0     # total contain groups processed
    _n_overlap_grps = 0     # total overlap groups processed
    _n_candidates   = 0     # total candidate points gathered (overlap)
    _n_ov_keys      = 0     # total occupied overlap cell-keys touched (cache hit or miss)
    _n_super_grps       = 0  # supercell fast-path: total groups processed
    _n_super_candidates = 0  # supercell fast-path: total candidates gathered across all groups
    _t_super_index_build = 0.0  # supercell fast-path: one-time target-index build (per nd, memoized)
    _t_super_loop        = 0.0  # supercell fast-path: sort + per-group loop (gather + distance-check)
    _t_super_gather      = 0.0  # supercell fast-path: candidate-gathering sub-step within the loop
    _t_super_distcheck   = 0.0  # supercell fast-path: distance-check + sum sub-step within the loop
    _t_block_cell_slices_build = 0.0  # nd>=0 path: block_cell_slices dict build, rebuilt every chunk
    _n_ov_template  = 0     # total template size (len(codec_offsets)) before occupancy filter
    _t_ov_filter    = 0.0   # gather sub-step: raw_keys build + occupancy filter + frozenset
    _t_ov_cachelkup = 0.0   # gather sub-step: overlap_fp_cache dict .get()
    _t_ov_copy      = 0.0   # gather sub-step: slice lookups + candidate_buffer copy loop
    _n_contain_pts  = 0     # total target-point × source-point pairs via contain path
    _n_o_cache_hit  = 0     # overlap_fp_cache hits
    _n_o_cache_miss = 0
    _n_buffer_resizes = 0   # candidate_buffer grew past its upfront max_cands estimate

    # ---- pre-loop invalid_area pass for exact super-cell mode -------------------
    # Cell-ID trigger: only pay the Shapely cost for points whose level-0 disk
    # cells (contain ∪ overlap) include at least one non-fully-interior cell.
    # Points are grouped by block (row // K, col // K): all points in the same
    # block share one clip polygon, so buffer + intersection + area are vectorized
    # across the whole group in a single C-level call.
    if _do_exact_supercell:
        # ---- Opt 1: vectorised trigger mask ---------------------------------
        # Build the dilation footprint as a symmetric disk of radius r_rows+1
        # cells, directly from the search radius/grid spacing -- NOT derived
        # from the per-region contain/overlap templates.
        #
        # BUG (found via VALIDATE_AREA, this session): the per-region
        # disk-reach templates (_cntd_cells_by_cell_region /
        # _ovlpd_cells_by_cell_region_va) are keyed by sub-cell (triangle)
        # position and are inherently one-directional in row for every
        # region (fp_row_min stayed 0 -- never negative -- even after
        # unioning ALL regions, not just active ones; this isn't a "which
        # regions are active" issue, the templates themselves never carry
        # negative row offsets). Using them to build the trigger-dilation
        # kernel silently shrinks its reach in one direction: a source point
        # could have real invalid/boundary cells within its true disk radius
        # that the kernel could never propagate into its trigger status --
        # points near a study_area edge got silently treated as fully
        # interior. A kernel derived straight from the radius has no such
        # asymmetry by construction. (+1 cell margin for float/round-off
        # safety at the boundary.)
        _trig_reach = r_rows + 1
        footprint_offsets: set = {
            (_dr, _dc)
            for _dr in range(-_trig_reach, _trig_reach + 1)
            for _dc in range(-_trig_reach, _trig_reach + 1)
            if _dr * _dr + _dc * _dc <= _trig_reach * _trig_reach
        }
        # Build 2D boolean kernel centred at (0,0).
        if footprint_offsets:
            footprint_arr  = np.array(sorted(footprint_offsets), dtype=np.int64)
            fp_row_min, fp_col_min = int(footprint_arr[:, 0].min()), int(footprint_arr[:, 1].min())
            fp_n_rows = int(footprint_arr[:, 0].max()) - fp_row_min + 1
            fp_n_cols = int(footprint_arr[:, 1].max()) - fp_col_min + 1
            dilation_kernel = np.zeros((fp_n_rows, fp_n_cols), dtype=bool)
            dilation_kernel[footprint_arr[:, 0] - fp_row_min, footprint_arr[:, 1] - fp_col_min] = True
        else:
            dilation_kernel = np.ones((1, 1), dtype=bool)
            fp_row_min = fp_col_min = 0
        # Build the trigger grid: mark every invalid and boundary (rr, cc) cell.
        # _invalid_cells_rc and _partly_src are both sets of (rr, cc) int tuples
        # already in scope for the exact path (compute_fractions=False).
        trig_row_min = int(rows.min()) + fp_row_min   # source rows shifted by kernel reach
        trig_col_min = int(cols.min()) + fp_col_min
        trig_row_max = int(rows.max()) - fp_row_min   # fp_row_min is negative → subtract negates
        trig_col_max = int(cols.max()) - fp_col_min
        trig_n_rows = trig_row_max - trig_row_min + 1
        trig_n_cols = trig_col_max - trig_col_min + 1
        trigger_grid = np.zeros((trig_n_rows, trig_n_cols), dtype=np.int8)
        partial_cells_rc = locals().get('partial_cells_rc') or set()
        for _rr, _cc in itertools.chain(_invalid_cells_rc, partial_cells_rc):
            _ri, _ci = _rr - trig_row_min, _cc - trig_col_min
            if 0 <= _ri < trig_n_rows and 0 <= _ci < trig_n_cols:
                trigger_grid[_ri, _ci] = 1
        # Dilate with the disk-shaped footprint; source points index directly into
        # the result — no boundary issues since source rows ∈ [rows.min, rows.max]
        # which maps to [−fp_row_min, trig_n_rows+fp_row_min−1] after the offset, always valid.
        try:
            from scipy.ndimage import maximum_filter as _max_flt
            trigger_dilated = _max_flt(
                trigger_grid.view(bool), footprint=dilation_kernel,
                mode='constant', cval=False,
            )
        except ImportError:
            if not getattr(_parse_area_weight, '_scipy_hint_shown', False):
                progress_print(
                    "Tip: install scipy for faster trigger dilation in area_weight='exact' "
                    "(falling back to numpy shift loop).")
                _parse_area_weight._scipy_hint_shown = True
            tg_bool = trigger_grid.view(bool)
            trigger_dilated = np.zeros((trig_n_rows, trig_n_cols), dtype=bool)
            for dr, dc in footprint_offsets:
                r_src_lo = max(0, -dr); r_src_hi = trig_n_rows - max(0, dr)
                r_dst_lo = r_src_lo + dr; r_dst_hi = r_src_hi + dr
                c_src_lo = max(0, -dc); c_src_hi = trig_n_cols - max(0, dc)
                c_dst_lo = c_src_lo + dc; c_dst_hi = c_src_hi + dc
                trigger_dilated[r_dst_lo:r_dst_hi, c_dst_lo:c_dst_hi] |= \
                    tg_bool[r_src_lo:r_src_hi, c_src_lo:c_src_hi]
        src_row_idx = rows - trig_row_min
        src_col_idx = cols - trig_col_min
        # Clip to grid bounds — source points outside the padded grid are untriggered.
        in_trigger_bounds = (
            (src_row_idx >= 0) & (src_row_idx < trig_n_rows) &
            (src_col_idx >= 0) & (src_col_idx < trig_n_cols)
        )
        triggered_mask = np.zeros(n_pts, dtype=bool)
        triggered_mask[in_trigger_bounds] = trigger_dilated[src_row_idx[in_trigger_bounds], src_col_idx[in_trigger_bounds]]
        triggered_indices = np.flatnonzero(triggered_mask)

        if not _vec_exact:
            # old per-point loop (vec=0) — used as benchmark baseline; trigger
            # check now vectorised, body unchanged for fair comparison.
            for _i in triggered_indices.tolist():
                home_class = _precise_home_cells.get((int(rows[_i]), int(cols[_i])), 'interior')
                if home_class == 'exterior':
                    invalid_area[_i] = full_disk_area
                elif home_class != 'interior':
                    _disk = _ShpPoint(
                        float(point_xy[_i, 0]), float(point_xy[_i, 1])
                    ).buffer(r, quad_segs=_quad_segs)
                    invalid_area[_i] = full_disk_area - _disk.intersection(home_class).area
        else:
            import shapely as _shp_vec
            block_buckets = {}   # (br, bc) -> list of point indices needing clip
            for _i in triggered_indices.tolist():
                home_class = _precise_home_cells.get((int(rows[_i]), int(cols[_i])), 'interior')
                if home_class == 'exterior':
                    invalid_area[_i] = full_disk_area
                elif home_class != 'interior':
                    block_key = (int(rows[_i]) // _K, int(cols[_i]) // _K)
                    if block_key not in block_buckets:
                        block_buckets[block_key] = []
                    block_buckets[block_key].append(_i)
            for (_br, _bc), pt_indices in block_buckets.items():
                clip_poly = _block_clips[(_br, _bc)]
                if len(pt_indices) < 4:
                    for _i in pt_indices:
                        _disk = _ShpPoint(
                            float(point_xy[_i, 0]), float(point_xy[_i, 1])
                        ).buffer(r, quad_segs=_quad_segs)
                        invalid_area[_i] = full_disk_area - _disk.intersection(clip_poly).area
                else:
                    _shp_vec.prepare(clip_poly)
                    _xy   = point_xy[pt_indices]
                    _disks = _shp_vec.buffer(
                        _shp_vec.points(_xy[:, 0], _xy[:, 1]), r, quad_segs=_quad_segs)
                    invalid_area[pt_indices] = (
                        full_disk_area - _shp_vec.area(_shp_vec.intersection(clip_poly, _disks)))

    progress = SearchProgress(silent=silent, n_pts=n_pts)
    progress.start()
    next_threshold = progress.next_threshold

    # ---- source row boundaries --------------------------------------------------
    # With disjoint-component scoping, a row VALUE can repeat across two
    # different GROUPS (e.g. a checkerboard gap pattern) since
    # component_group_id is the primary sort key -- must also break on group
    # change, or two disjoint groups sharing a row number would be merged
    # into one "unique row" entry. Deliberately component_group_id, NOT raw
    # component_id: the row+col planner's own hard break already uses
    # component_group_id, and rows from different raw clusters WITHIN the
    # same group are meant to collapse together here exactly like the
    # non-disjoint path does (confirmed this session: breaking on raw
    # component_id here instead inflated n_atomic_rows ~13x on a 3-tile
    # stress test -- every raw-cluster transition inside an already-merged
    # group forced a needless extra row split, since a shared row value
    # across two different raw clusters in the SAME group never gets a
    # chance to collapse into one atomic row the way it would if the whole
    # group were processed as an ordinary standalone dataset).
    if component_group_id is not None:
        src_row_bdry = _np_flatnonzero(np.concatenate(
            [[True], (rows[1:] != rows[:-1]) | (component_group_id[1:] != component_group_id[:-1])]))
    else:
        src_row_bdry  = _np_flatnonzero(
            np.concatenate([[True], rows[1:] != rows[:-1]]))
    src_unique_rows = rows[src_row_bdry]
    n_src_unique    = len(src_unique_rows)

    contain_starts_all = _np_flatnonzero(contain_changed)
    contain_ends_all   = _np_append(contain_starts_all[1:], n_pts)
    overlap_starts_all = _np_flatnonzero(overlap_changed)
    overlap_ends_all   = _np_append(overlap_starts_all[1:], n_pts)

    # candidate buffer — reused across overlap groups
    candidate_buffer = _np_zeros((max(max_cands, 1), n_c + 2), dtype=float)

    # ---- two pointers into tgt sorted array -------------------------------------
    blk_lo = 0  # index of first tgt pt in current block
    blk_hi = 0  # index past last tgt pt in current block
    # tracks which target row range is currently loaded
    loaded_row_lo = None
    loaded_row_hi = None
    # tracks which disjoint group's array blk_lo/blk_hi currently index into
    loaded_group_id = None

    # ---- coarse density map for per-chunk ppc estimation -----------------------
    # Sparse map: only OCCUPIED coarse cells are stored (as parallel arrays,
    # sorted in row-major order by coarse key = cr*width+cc), never a dense
    # (n_rows x n_cols) array. This matters because a single target point far
    # from the rest of the cloud can make the row/col span — and therefore a
    # dense array sized to it — enormous, even though the actual occupied-cell
    # count stays proportional to the data. `_max_cc_coarse` ("width") below is
    # only ever used as a cheap int64 key multiplier, never as an allocation size.
    # Built from TARGET pts (the data being summed), not source pts — ppc
    # reflects how many target pts each source cell will encounter.
    # K aligns with r_rows so each coarse cell is roughly one disk-radius wide.
    _K = max(4, r_rows)
    if n_tgt > 0:
        _cr_arr = (tgt_rows_full // _K).astype(np.int64)
        _cc_arr = (tgt_cols_full // _K).astype(np.int64)
        _max_cc_coarse = int(_cc_arr.max()) + 1
        _max_cr_coarse = int(_cr_arr.max()) + 1
        # flat keys: sort by (coarse_key, fine_key) so fine keys are ordered per group
        _coarse_key = _cr_arr * _max_cc_coarse + _cc_arr
        _fine_key   = tgt_rows_full * (_max_cc_coarse * _K + 1) + tgt_cols_full
        _order      = np.lexsort((_fine_key, _coarse_key))
        _ck_s = _coarse_key[_order]
        _fk_s = _fine_key[_order]
        # group boundaries in sorted array
        _new_coarse        = np.empty(n_tgt, dtype=bool)
        _new_coarse[0]     = True
        _new_coarse[1:]    = _ck_s[1:] != _ck_s[:-1]
        _new_fine          = np.empty(n_tgt, dtype=bool)
        _new_fine[0]       = True
        _new_fine[1:]      = _fk_s[1:] != _fk_s[:-1]  # True at coarse AND fine boundaries
        _cs_starts         = np.flatnonzero(_new_coarse)
        _cs_ends           = np.empty_like(_cs_starts)
        _cs_ends[:-1]      = _cs_starts[1:]
        _cs_ends[-1]       = n_tgt
        _n_pts_cc          = (_cs_ends - _cs_starts).astype(np.float32)
        # n unique fine cells per coarse group via cumsum of new_fine
        _fcum              = np.cumsum(_new_fine)
        _n_fine_cc         = (_fcum[_cs_ends - 1]
                              - np.concatenate([[0], _fcum[_cs_starts[1:] - 1]])).astype(np.float32)
        _ppc_cc            = _n_pts_cc / (_K * _K) * math.pi * _sr ** 2
        # sparse arrays, length = n_occupied_coarse_cells (NOT max_cr*max_cc)
        _ck_unique         = _ck_s[_cs_starts]          # sorted row-major coarse keys
        # 1-D row profiles for density-guided row cuts, built by grouping the
        # already-sparse per-cell arrays by cr (row-major sort keeps same-cr
        # entries contiguous), never touching a dense (row x col) structure.
        # Mean ppc: used as the zone anchor for the current strip's start row —
        #   stable, not distorted by single hot cells.
        # Max ppc: used as the zone-change trigger for subsequent rows —
        #   catches a single dense hot-spot column that mean would bury.
        _cr_of_cc      = _ck_unique // _max_cc_coarse
        _row_bdry      = np.empty(len(_ck_unique), dtype=bool)
        _row_bdry[0]   = True
        _row_bdry[1:]  = _cr_of_cc[1:] != _cr_of_cc[:-1]
        _row_starts    = np.flatnonzero(_row_bdry)
        _row_cr_unique = _cr_of_cc[_row_starts]
        _row_pts_s     = np.add.reduceat(_n_pts_cc, _row_starts)
        _row_fine_s    = np.add.reduceat(_n_fine_cc, _row_starts)
        _row_ppc_max_s = np.maximum.reduceat(_ppc_cc, _row_starts)
        _row_ppc_mean_s = np.where(_row_fine_s > 0, _row_pts_s / _row_fine_s, 0.0).astype(np.float32)
        # Per-occupied-cell disjoint-component label, computed once here
        # alongside the density map itself (see _label_components_from_cell_keys
        # docstring) so downstream density queries can tell "this cell belongs
        # to the same physically-connected region as that cell" apart from
        # "this cell's row number happens to fall in the same numeric range."
        # Cheap: reuses the already-sorted, already-deduped _ck_unique array,
        # no extra pass over raw points needed.
        _ck_component = _label_components_from_cell_keys(_ck_unique, _max_cc_coarse)
        # Dominant-MERGED-GROUP label per occupied coarse cell (from the
        # combined source+target union-find, config.USE_DISJOINT_COMPONENT_SCOPING).
        # Takes the FIRST point's label per cell group rather than a full mode
        # computation -- a coarse cell straddling a genuine disjoint-cluster
        # gap would be a very rare edge case (the gap is real by construction,
        # cells are small relative to it in the vast majority of datasets), so
        # this is a deliberate simplicity/speed tradeoff, not assumed exact.
        # Deliberately component_GROUP_id (_tgt_component_group_id), NOT raw
        # _tgt_component_id: every caller (_row_profile_cluster,
        # _row_range_profile_cluster, the per-group cache refresh in
        # _plan_chunks_clean) compares _ck_cluster against component_group_id
        # values. Using raw component_id here was a real correctness bug --
        # confirmed this session via a 10-tile/r=3000 stress test where 31,900
        # of 151,610 points silently got a target sum of 0.0 instead of their
        # correct value. Raw component ids and merged group ids are numbered
        # independently (group ids restart at 0 in ascending-size-fold order;
        # raw ids come from the union-find's own compression order), so they
        # only coincidentally lined up often enough to pass MATCH checks at
        # some radii -- changing r changes which raw clusters merge (this
        # union-find itself runs at radius r), which changes the raw-id
        # numbering and broke the coincidental alignment at r=3000.
        # _ck_cluster itself is dead: _row_profile_cluster/_row_range_profile_
        # cluster read from the per-group local cache (_cluster_cache)
        # instead, not this global array -- see their docstrings. Left
        # unpopulated (always empty) rather than computed and discarded, now
        # that _use_disjoint_scoping is always True (previously this branch
        # only ran when disjoint scoping was on, so the waste was at least
        # conditional; unconditionally routing through disjoint mode made it
        # unconditional too).
        _ck_cluster = np.empty(0, dtype=np.int64)
    else:  # n_tgt == 0
        _max_cr_coarse = 1; _max_cc_coarse = 1
        _ck_unique = np.empty(0, dtype=np.int64)
        _ppc_cc    = np.empty(0, dtype=np.float32)
        _n_pts_cc  = np.empty(0, dtype=np.float32)
        _ck_component   = np.empty(0, dtype=np.int64)
        _ck_cluster     = np.empty(0, dtype=np.int64)
        _row_cr_unique  = np.empty(0, dtype=np.int64)
        _row_ppc_mean_s = np.empty(0, dtype=np.float32)
        _row_ppc_max_s  = np.empty(0, dtype=np.float32)

    def _build_density_map_from_slice(_tr, _tc):
        """Same construction as the GLOBAL target density map above
        (coarse-key sort, per-cell point counts, per-row mean/max ppc), just
        callable on an arbitrary (rows, cols) slice -- used to build a
        group's OWN density map directly from its own contiguous target-
        point slice (see the per-group cache refresh below), instead of
        building one global map and masking it down per group. Masking a
        global array of size proportional to the WHOLE dataset, once per
        group, made total cost O(n_groups * total_size) -- confirmed this
        session via a 3/10/30-tile scaling check that this was genuinely
        quadratic in tile count (9.96x and 8.14x work for ~3x more tiles).
        Building fresh from an already-isolated slice is O(that group's own
        size) instead, with no cross-group data ever touched. Coarse keys
        use the same _K/_max_cc_coarse as the global map so callers can
        decode them (key % _max_cc_coarse) identically either way."""
        _n = len(_tr)
        if _n == 0:
            _e_i = np.empty(0, dtype=np.int64)
            _e_f = np.empty(0, dtype=np.float32)
            return _e_i, _e_f, _e_f, _e_i, _e_i, _e_f, _e_f
        _g_cr_arr = (_tr // _K).astype(np.int64)
        _g_cc_arr = (_tc // _K).astype(np.int64)
        _g_coarse_key = _g_cr_arr * _max_cc_coarse + _g_cc_arr
        _g_fine_key = _tr * (_max_cc_coarse * _K + 1) + _tc
        _g_order = np.lexsort((_g_fine_key, _g_coarse_key))
        _g_ck_s = _g_coarse_key[_g_order]
        _g_fk_s = _g_fine_key[_g_order]
        _g_new_coarse = np.empty(_n, dtype=bool)
        _g_new_coarse[0] = True
        _g_new_coarse[1:] = _g_ck_s[1:] != _g_ck_s[:-1]
        _g_new_fine = np.empty(_n, dtype=bool)
        _g_new_fine[0] = True
        _g_new_fine[1:] = _g_fk_s[1:] != _g_fk_s[:-1]
        _g_cs_starts = np.flatnonzero(_g_new_coarse)
        _g_cs_ends = np.empty_like(_g_cs_starts)
        _g_cs_ends[:-1] = _g_cs_starts[1:]
        _g_cs_ends[-1] = _n
        _g_n_pts_cc = (_g_cs_ends - _g_cs_starts).astype(np.float32)
        _g_fcum = np.cumsum(_g_new_fine)
        _g_n_fine_cc = (_g_fcum[_g_cs_ends - 1]
                        - np.concatenate([[0], _g_fcum[_g_cs_starts[1:] - 1]])).astype(np.float32)
        _g_ppc_cc = _g_n_pts_cc / (_K * _K) * math.pi * _sr ** 2
        _g_ck_unique = _g_ck_s[_g_cs_starts]
        _g_cr_of_cc = _g_ck_unique // _max_cc_coarse
        _g_row_bdry = np.empty(len(_g_ck_unique), dtype=bool)
        _g_row_bdry[0] = True
        _g_row_bdry[1:] = _g_cr_of_cc[1:] != _g_cr_of_cc[:-1]
        _g_row_starts = np.flatnonzero(_g_row_bdry)
        _g_row_cr_unique = _g_cr_of_cc[_g_row_starts]
        _g_row_pts_s = np.add.reduceat(_g_n_pts_cc, _g_row_starts)
        _g_row_fine_s = np.add.reduceat(_g_n_fine_cc, _g_row_starts)
        _g_row_ppc_max_s = np.maximum.reduceat(_g_ppc_cc, _g_row_starts)
        _g_row_ppc_mean_s = np.where(_g_row_fine_s > 0, _g_row_pts_s / _g_row_fine_s, 0.0).astype(np.float32)
        return (_g_ck_unique, _g_n_pts_cc, _g_ppc_cc, _g_row_cr_unique, _g_row_starts,
                _g_row_ppc_mean_s, _g_row_ppc_max_s)

    # SOURCE-point analogue of _tgt_grp_range -- no extra sort needed here,
    # since pts_source (and therefore rows/cols) is ALREADY sorted with
    # component_group_id as the primary key, so each group's own source
    # points are already one contiguous slice [start, end) directly in
    # rows/cols. Feeds the per-group SOURCE density-map cache below (used
    # by USE_SOURCE_WEIGHTED_FIT_PENALTY) -- this was the remaining "sees
    # more than its own points" gap: _row_profile_src/_row_range_profile_src
    # read from the GLOBAL source density map (_ck_unique_src etc, built
    # from ALL groups' source points combined), so every group's
    # source-weighted _fit_penalty call was unioning against however much
    # of the WHOLE dataset's source points happened to share its coarse row
    # -- confirmed this session via a _same_nd_mechanism call-count trace
    # that scaled roughly quadratically with tile count even after the
    # target-side density map was fixed to be properly per-group isolated.
    if _use_disjoint_scoping and component_group_id is not None:
        _src_grp_bdry = np.flatnonzero(np.concatenate(
            [[True], component_group_id[1:] != component_group_id[:-1]]))
        _src_grp_bdry_ids = component_group_id[_src_grp_bdry]
        _src_grp_ends = np.append(_src_grp_bdry[1:], n_pts)
        _src_grp_range = np.full((_cgrp_next, 2), -1, dtype=np.int64)
        _src_grp_range[_src_grp_bdry_ids, 0] = _src_grp_bdry
        _src_grp_range[_src_grp_bdry_ids, 1] = _src_grp_ends
    else:
        _src_grp_range = None

    # Per-group local density-map cache for _row_profile_cluster/
    # _row_range_profile_cluster (config.USE_DISJOINT_COMPONENT_SCOPING):
    # refreshed once per group by _plan_chunks_clean's outer loop (built
    # fresh from that group's own isolated target-point slice via
    # _build_density_map_from_slice), then reused by every row lookup and
    # prefilter check within that group.
    _cluster_cache_id = [None]
    _cluster_cache = [(np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32),
                       np.empty(0, dtype=np.int64), np.empty(0, dtype=np.int64),
                       np.empty(0, dtype=np.float32), np.empty(0, dtype=np.float32))]
    # SOURCE-side counterpart (see _src_grp_range docstring above for why
    # this is needed) -- same shape/refresh pattern, separate cache slot.
    _cluster_cache_src = [(np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32),
                           np.empty(0, dtype=np.int64), np.empty(0, dtype=np.int64),
                           np.empty(0, dtype=np.float32), np.empty(0, dtype=np.float32))]

    # ---- coarse density map for SOURCE points, mirroring the target one
    # above EXACTLY (same _K bucketing, same construction) -- built only
    # when config.USE_SOURCE_WEIGHTED_FIT_PENALTY requests it. Deliberately
    # built the identical way as the target map so that when source==target
    # (self-search, the common case), _ck_unique_src/_n_pts_cc_src come out
    # numerically IDENTICAL to _ck_unique/_n_pts_cc -- guaranteeing the
    # source-weighted _fit_penalty variant reduces to the exact same
    # decision as the target-weighted one whenever source and target are
    # the same point set, rather than an independently-scoped approximation
    # (see this session's discussion: a per-fine-row source slice does NOT
    # match a per-coarse-row-band target lookup, even for self-search --
    # only mirroring the SAME coarse-row-band aggregation guarantees exact
    # equivalence at src==tgt).
    _use_src_weighted_penalty = getattr(_cfg, 'USE_SOURCE_WEIGHTED_FIT_PENALTY', False)
    if _use_src_weighted_penalty and n_pts > 0:
        _cr_arr_s = (rows // _K).astype(np.int64)
        _cc_arr_s = (cols // _K).astype(np.int64)
        _coarse_key_s = _cr_arr_s * _max_cc_coarse + _cc_arr_s
        _fine_key_s   = rows * (_max_cc_coarse * _K + 1) + cols
        _order_s      = np.lexsort((_fine_key_s, _coarse_key_s))
        _ck_s_s = _coarse_key_s[_order_s]
        _new_coarse_s        = np.empty(n_pts, dtype=bool)
        _new_coarse_s[0]     = True
        _new_coarse_s[1:]    = _ck_s_s[1:] != _ck_s_s[:-1]
        _cs_starts_s         = np.flatnonzero(_new_coarse_s)
        _cs_ends_s           = np.empty_like(_cs_starts_s)
        _cs_ends_s[:-1]      = _cs_starts_s[1:]
        _cs_ends_s[-1]       = n_pts
        _n_pts_cc_src        = (_cs_ends_s - _cs_starts_s).astype(np.float64)
        _ck_unique_src       = _ck_s_s[_cs_starts_s]
        _cr_of_cc_src   = _ck_unique_src // _max_cc_coarse
        _row_bdry_src   = np.empty(len(_ck_unique_src), dtype=bool)
        _row_bdry_src[0] = True
        _row_bdry_src[1:] = _cr_of_cc_src[1:] != _cr_of_cc_src[:-1]
        _row_starts_src  = np.flatnonzero(_row_bdry_src)
        _row_cr_unique_src = _cr_of_cc_src[_row_starts_src]
    else:
        _ck_unique_src = np.empty(0, dtype=np.int64)
        _n_pts_cc_src  = np.empty(0, dtype=np.float64)
        _row_starts_src = np.empty(0, dtype=np.int64)
        _row_cr_unique_src = np.empty(0, dtype=np.int64)

    def _sparse_row_lookup(cr, unique_arr, val_arr, default=0.0):
        """Single-row value lookup (mean/max ppc) via binary search — O(log n)."""
        i = int(np.searchsorted(unique_arr, cr))
        if i < len(unique_arr) and unique_arr[i] == cr:
            return float(val_arr[i])
        return default

    def _sparse_box_ppc(cr_lo, cr_hi, cc_lo=None, cc_hi=None):
        """All occupied-cell ppc values with cr in [cr_lo, cr_hi] (optionally cc too)."""
        if len(_ck_unique) == 0:
            return _ppc_cc[:0]
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        if cc_lo is None:
            return _ppc_cc[i0:i1]
        cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
        m = (cc_vals >= cc_lo) & (cc_vals <= cc_hi)
        return _ppc_cc[i0:i1][m]

    def _sparse_box_ppc_coherent(cr_lo, cr_hi, cc_lo=None, cc_hi=None):
        """Like _sparse_box_ppc, but guards against the "A and C, gap B"
        problem: a numeric [cr_lo, cr_hi] row range can span more than one
        disjoint physical region even though every occupied cell it finds is
        real (no wasted scan over the empty gap -- that part was already
        fixed). Without this guard, the returned ppc sample silently blends
        whichever regions happen to fall in the range, producing a density
        estimate that represents neither region well.

        Uses _ck_component (computed once alongside the density map) to find
        the single DOMINANT component (most occupied cells) within the
        queried range, then returns only that component's cells. This makes
        "is this a coherent region" an explicit cell-label check instead of
        an implicit, easily-violated assumption about row-range membership.

        Returns (ppc_values, is_coherent): is_coherent=False means the range
        as queried spans >1 component and the result was narrowed to the
        dominant one -- a signal callers can use (e.g. to avoid merging that
        stretch into one strip in the first place).
        """
        if len(_ck_unique) == 0:
            return _ppc_cc[:0], True
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        if cc_lo is not None:
            cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
            m = (cc_vals >= cc_lo) & (cc_vals <= cc_hi)
        else:
            m = None
        _ppc_slice = _ppc_cc[i0:i1] if m is None else _ppc_cc[i0:i1][m]
        _comp_slice = _ck_component[i0:i1] if m is None else _ck_component[i0:i1][m]
        if len(_comp_slice) == 0:
            return _ppc_slice, True
        _uniq_comp, _counts = np.unique(_comp_slice, return_counts=True)
        if len(_uniq_comp) == 1:
            return _ppc_slice, True
        _dominant = _uniq_comp[np.argmax(_counts)]
        return _ppc_slice[_comp_slice == _dominant], False

    def _sparse_box_ppc_and_counts(cr_lo, cr_hi, cc_lo=None, cc_hi=None):
        """Same slicing as _sparse_box_ppc, but also returns each occupied
        block's raw point count (_n_pts_cc) alongside its ppc -- needed for
        best_nd_tag_weighted's point-count-weighted sum (see cfg.
        USE_WEIGHTED_ND_DECISION). Point counts are what _ppc_cc was derived
        from in the first place (_ppc_cc = _n_pts_cc/(K*K)*pi*sr^2), sliced
        identically so the two arrays stay in matching order."""
        if len(_ck_unique) == 0:
            return _ppc_cc[:0], _n_pts_cc[:0]
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        if cc_lo is None:
            return _ppc_cc[i0:i1], _n_pts_cc[i0:i1]
        cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
        m = (cc_vals >= cc_lo) & (cc_vals <= cc_hi)
        return _ppc_cc[i0:i1][m], _n_pts_cc[i0:i1][m]

    def _sparse_box_ppc_and_counts_coherent(cr_lo, cr_hi, cc_lo=None, cc_hi=None):
        """Component-aware counterpart of _sparse_box_ppc_and_counts, mirroring
        _sparse_box_ppc_coherent -- this is the variant _exact_chunk_nd actually
        needs (the USE_WEIGHTED_ND_DECISION=True default path uses counts, not
        the plain ppc percentile), which was the real gap: _exact_chunk_nd is
        what decides the nd tag actually used by the hot loop, and it was never
        wired to any coherent estimate -- only the (now-removed) legacy
        planner's row-strip tag selection was, which turned out
        not to be where the real -1-vs-15 divergence under
        USE_DISJOINT_COMPONENT_SCOPING comes from."""
        if len(_ck_unique) == 0:
            return _ppc_cc[:0], _n_pts_cc[:0]
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        if cc_lo is not None:
            cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
            m = (cc_vals >= cc_lo) & (cc_vals <= cc_hi)
        else:
            m = None
        _ppc_slice = _ppc_cc[i0:i1] if m is None else _ppc_cc[i0:i1][m]
        _cnt_slice = _n_pts_cc[i0:i1] if m is None else _n_pts_cc[i0:i1][m]
        _comp_slice = _ck_component[i0:i1] if m is None else _ck_component[i0:i1][m]
        if len(_comp_slice) == 0:
            return _ppc_slice, _cnt_slice
        _uniq_comp, _counts = np.unique(_comp_slice, return_counts=True)
        if len(_uniq_comp) == 1:
            return _ppc_slice, _cnt_slice
        _dominant = _uniq_comp[np.argmax(_counts)]
        _dom_mask = _comp_slice == _dominant
        return _ppc_slice[_dom_mask], _cnt_slice[_dom_mask]

    def _sparse_col_percentile(cr_lo, cr_hi, cc_lo, cc_hi, pct):
        """Per-occupied-column percentile of cell ppc within [cr_lo,cr_hi] x [cc_lo,cc_hi].

        Returns (cc_values, pct_ppc) — sparse, only occupied columns — rather
        than a dense array indexed 0..n_coarse_cols-1 (n_coarse_cols can be the
        full grid width; bounding to the strip's cc range keeps this cheap).

        Cell-based (unweighted by how many source points happen to sit in a
        column), unlike a per-point average — a column with many points at
        moderate density shouldn't outrank a column with fewer points at
        genuinely higher spatial density. pct=100 recovers the old max
        behaviour (used as the strip-level hot-spot trigger); pct=75 is used
        for actual per-column zone classification, matching the strip-level
        _PPC_CHUNK_PERCENTILE convention.
        """
        if len(_ck_unique) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32)
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
        vals    = _ppc_cc[i0:i1]
        m = (cc_vals >= cc_lo) & (cc_vals <= cc_hi)
        cc_vals = cc_vals[m]; vals = vals[m]
        if len(cc_vals) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32)
        order = np.argsort(cc_vals, kind='stable')
        cc_s = cc_vals[order]; v_s = vals[order]
        bdry = np.empty(len(cc_s), dtype=bool)
        bdry[0] = True
        bdry[1:] = cc_s[1:] != cc_s[:-1]
        starts = np.flatnonzero(bdry)
        ends = np.empty_like(starts); ends[:-1] = starts[1:]; ends[-1] = len(cc_s)
        out = np.empty(len(starts), dtype=np.float32)
        for _gi in range(len(starts)):
            out[_gi] = np.percentile(v_s[starts[_gi]:ends[_gi]], pct)
        return cc_s[starts], out

    def _sparse_col_sum_ppc(cr_lo, cr_hi, cc_lo, cc_hi):
        """Per-occupied-column TOTAL point count within [cr_lo,cr_hi] x
        [cc_lo,cc_hi], converted to ppc -- an EXACT sum of the real
        per-cell point counts already available in _n_pts_cc, not a
        percentile-of-cell-ppc-values statistic. Cheaper too: one
        scatter-add, no sorting/ranking needed, and immune to the
        percentile-choice dilution that let real per-column density
        variation go undetected (confirmed this session: a 64-region
        synthetic grid with density spanning 0.01-10,000 ppc never
        produced more than 1-2 column chunks, because _sparse_col_percentile
        summarized each column's density with a single percentile computed
        across the strip's full row range instead of the column's actual
        point mass)."""
        if len(_ck_unique) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32)
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
        cnts    = _n_pts_cc[i0:i1]
        m = (cc_vals >= cc_lo) & (cc_vals <= cc_hi)
        cc_vals = cc_vals[m]; cnts = cnts[m]
        if len(cc_vals) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32)
        uniq_cc, inv = np.unique(cc_vals, return_inverse=True)
        sums = np.zeros(len(uniq_cc), dtype=np.float64)
        np.add.at(sums, inv, cnts)
        _row_span = cr_hi - cr_lo + 1
        _col_ppc = (sums / (_K * _K * _row_span)) * math.pi * _sr ** 2
        return uniq_cc, _col_ppc.astype(np.float32)

    # ==========================================================================
    # The chunk planner -- avoids the 1D-marginal blind spot the old (now
    # removed) row/column density-blending planner had: that planner scanned
    # rows using a statistic blended across the FULL column width, then
    # (separately) scanned columns using a statistic blended across the full
    # row range of whatever strip resulted -- neither scan could ever see the
    # other axis's real structure, and no amount of patching the comparison
    # logic downstream (magnitude-aware banding, exact per-column sums, etc.)
    # could recover information already lost
    # upstream. This planner instead grows row-groups by comparing each
    # candidate row's REAL sparse (column -> point count) profile against
    # the group's accumulated profile (a merge-join, not a blended scalar),
    # and reuses that same accumulated profile directly for column banding
    # once a group closes -- no separate, redundant column density lookup.
    # A prototype: computed in parallel with (not yet replacing) the legacy
    # planner; when the flag is on, its output overrides _strip_plans below.
    # ==========================================================================
    def _row_profile(cr):
        """Sparse (occupied_columns, point_counts) for exactly one coarse
        row, via direct binary search into the row-boundary index already
        built alongside the density map -- no per-cell scan."""
        i = int(np.searchsorted(_row_cr_unique, cr))
        if i >= len(_row_cr_unique) or _row_cr_unique[i] != cr:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32)
        i0 = int(_row_starts[i])
        i1 = int(_row_starts[i + 1]) if i + 1 < len(_row_starts) else len(_ck_unique)
        return _ck_unique[i0:i1] % _max_cc_coarse, _n_pts_cc[i0:i1]

    def _row_profile_cluster(cr, cluster_id):
        """Cluster-scoped variant of _row_profile (config.USE_DISJOINT_COMPONENT_SCOPING):
        same lookup, but discards any occupied cell whose dominant source
        cluster (_ck_cluster) doesn't match cluster_id -- avoids handing the
        row-scan/_fit_penalty irrelevant density data from OTHER disjoint
        clusters that happen to share this coarse row (e.g. clusters
        separated primarily by column, not row -- see the 10-tile stress
        test this session). Pure efficiency: discarded cells are still
        correctly disjoint from cluster_id's own points (different columns),
        so this never changes correctness, only how much irrelevant data
        _fit_penalty/_band_from_profile have to sift through.

        Uses the per-group local cache (refreshed once per group by
        _plan_chunks_clean, keyed by cluster_id) instead of masking the
        global _ck_unique/_ck_cluster arrays on every call -- same result,
        but the group-restriction filter now costs O(group's own cells)
        once per group instead of O(all groups' cells in this row) per
        call."""
        assert _cluster_cache_id[0] == cluster_id, 'cluster cache not refreshed for this group'
        _g_ck_unique, _g_n_pts_cc, _g_row_cr_unique, _g_row_starts, _g_row_ppc_mean_s, _g_row_ppc_max_s = _cluster_cache[0]
        i = int(np.searchsorted(_g_row_cr_unique, cr))
        if i >= len(_g_row_cr_unique) or _g_row_cr_unique[i] != cr:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float32)
        i0 = int(_g_row_starts[i])
        i1 = int(_g_row_starts[i + 1]) if i + 1 < len(_g_row_starts) else len(_g_ck_unique)
        return _g_ck_unique[i0:i1] % _max_cc_coarse, _g_n_pts_cc[i0:i1]

    def _half_row_profile(fine_row_lo, fine_row_hi):
        """Target profile for an arbitrary FINE row range [fine_row_lo,
        fine_row_hi) -- direct range query against tgt_rows_full/tgt_cols_full
        (already globally sorted by row, the function's primary sort key),
        not the coarse density map (which only has K-row resolution and can't
        express a half-row window). Used by _row_profile_diluted to pull in
        only the near-K/2 fine rows of each neighbor, not the whole
        neighboring coarse row."""
        i0 = int(_np_searchsorted(tgt_rows_full, fine_row_lo, side='left'))
        i1 = int(_np_searchsorted(tgt_rows_full, fine_row_hi, side='left'))
        if i1 <= i0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        _cc = tgt_cols_full[i0:i1] // _K
        _uniq_cc, _counts = np.unique(_cc, return_counts=True)
        return _uniq_cc, _counts.astype(np.float64)

    def _row_profile_diluted(cr):
        """Own-cell-weighted, row-neighbor-diluted variant of _row_profile
        (config.USE_ROW_DILUTED_PENALTY). own row's OWN profile dominates
        (weight ROW_DILUTION_OWN_WEIGHT); the remainder comes from the
        AVERAGE of whichever of {top neighbor's near half, bottom neighbor's
        near half} actually exist (own excluded -- it already has its own
        explicit weight, this isn't a 3-way average). Only the K//2 fine rows
        of each neighbor closest to the shared boundary are used (not the
        whole neighboring coarse row) -- keeps the dilution local to the
        boundary itself rather than pulling in an entire extra row-height of
        unrelated density. Falls back to plain _row_profile(cr) when neither
        neighbor half has any points."""
        own_cols, own_pts = _row_profile(cr)
        _half = max(1, _K // 2)
        top_cols, top_pts = _half_row_profile(cr * _K - _half, cr * _K)
        bot_cols, bot_pts = _half_row_profile((cr + 1) * _K, (cr + 1) * _K + _half)
        _n_neighbors = (1 if len(top_cols) else 0) + (1 if len(bot_cols) else 0)
        if _n_neighbors == 0:
            return own_cols, own_pts.astype(np.float64)
        _nb_cols, _nb_sum = _merge_profiles(top_cols, top_pts, bot_cols, bot_pts)
        _nb_avg = _nb_sum / _n_neighbors
        _w_own = getattr(_cfg, 'ROW_DILUTION_OWN_WEIGHT', 0.66)
        return _merge_profiles(own_cols, own_pts.astype(np.float64) * _w_own,
                                _nb_cols, _nb_avg * (1.0 - _w_own))

    def _row_profile_src(cr):
        """SOURCE-point analogue of _row_profile -- same coarse row bucket
        cr, same _K-column bucketing, looked up from the SOURCE density map
        (_ck_unique_src/_n_pts_cc_src) built identically to the target one.
        When source==target this returns numerically the same thing
        _row_profile(cr) would (see config.USE_SOURCE_WEIGHTED_FIT_PENALTY
        docstring on the map construction above for why that equivalence
        matters -- a per-fine-row slice would NOT have matched, only a
        same-coarse-row-band lookup does)."""
        i = int(np.searchsorted(_row_cr_unique_src, cr))
        if i >= len(_row_cr_unique_src) or _row_cr_unique_src[i] != cr:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        i0 = int(_row_starts_src[i])
        i1 = int(_row_starts_src[i + 1]) if i + 1 < len(_row_starts_src) else len(_ck_unique_src)
        return _ck_unique_src[i0:i1] % _max_cc_coarse, _n_pts_cc_src[i0:i1]

    def _row_range_profile_src(cr_lo, cr_hi):
        """SOURCE-point analogue of _row_range_profile -- contiguous coarse
        row range [cr_lo, cr_hi], one range query, mirrors the target
        version exactly against the source density map."""
        if len(_ck_unique_src) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique_src, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique_src, key_hi, side='left'))
        if i1 <= i0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        cc_vals = _ck_unique_src[i0:i1] % _max_cc_coarse
        cnts = _n_pts_cc_src[i0:i1]
        uniq_cc, inv = np.unique(cc_vals, return_inverse=True)
        sums = np.zeros(len(uniq_cc), dtype=np.float64)
        np.add.at(sums, inv, cnts)
        return uniq_cc, sums

    def _row_profile_src_cluster(cr, cluster_id):
        """Cluster-scoped variant of _row_profile_src -- see
        _row_profile_cluster's docstring for the rationale. Uses the
        per-group SOURCE cache (_cluster_cache_src, refreshed alongside the
        target cache above) instead of the global source density map."""
        assert _cluster_cache_id[0] == cluster_id, 'cluster cache not refreshed for this group'
        _gs_ck_unique, _gs_n_pts_cc, _gs_row_cr_unique, _gs_row_starts, _gs_row_ppc_mean_s, _gs_row_ppc_max_s = _cluster_cache_src[0]
        i = int(np.searchsorted(_gs_row_cr_unique, cr))
        if i >= len(_gs_row_cr_unique) or _gs_row_cr_unique[i] != cr:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        i0 = int(_gs_row_starts[i])
        i1 = int(_gs_row_starts[i + 1]) if i + 1 < len(_gs_row_starts) else len(_gs_ck_unique)
        return _gs_ck_unique[i0:i1] % _max_cc_coarse, _gs_n_pts_cc[i0:i1]

    def _row_range_profile_src_cluster(cr_lo, cr_hi, cluster_id):
        """Cluster-scoped variant of _row_range_profile_src -- see
        _row_profile_src_cluster docstring."""
        assert _cluster_cache_id[0] == cluster_id, 'cluster cache not refreshed for this group'
        _gs_ck_unique, _gs_n_pts_cc, _gs_row_cr_unique, _gs_row_starts, _gs_row_ppc_mean_s, _gs_row_ppc_max_s = _cluster_cache_src[0]
        if len(_gs_ck_unique) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_gs_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_gs_ck_unique, key_hi, side='left'))
        if i1 <= i0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        cc_vals = _gs_ck_unique[i0:i1] % _max_cc_coarse
        cnts = _gs_n_pts_cc[i0:i1]
        uniq_cc, inv = np.unique(cc_vals, return_inverse=True)
        sums = np.zeros(len(uniq_cc), dtype=np.float64)
        np.add.at(sums, inv, cnts)
        return uniq_cc, sums

    def _merge_profiles(cols_a, pts_a, cols_b, pts_b):
        """Union two sparse column->pts profiles, summing pts on shared columns."""
        if len(cols_a) == 0:
            return cols_b, pts_b.astype(np.float64)
        if len(cols_b) == 0:
            return cols_a, pts_a.astype(np.float64)
        all_cols = np.union1d(cols_a, cols_b)
        out = np.zeros(len(all_cols), dtype=np.float64)
        out[np.searchsorted(all_cols, cols_a)] += pts_a
        out[np.searchsorted(all_cols, cols_b)] += pts_b
        return all_cols, out

    # (#3) Exact-ppc memoization for _best_nd_tag: _sr is constant for the
    # whole call, so the cache key is just the ppc value. Zero precision
    # impact (same input always maps to the same cached output) -- purely
    # avoids re-running the keyframe-interpolation lookup in nd_choice.py
    # for a repeated ppc, which is common (many sparse columns share the
    # same small integer point count over the same row span).
    _nd_tag_cache = {}
    def _cached_best_nd_tag(ppc):
        _key = float(ppc)
        _tag = _nd_tag_cache.get(_key)
        if _tag is None:
            _tag = _best_nd_tag(_sr, _key)
            _nd_tag_cache[_key] = _tag
        return _tag

    def _col_ppc_tags(cols, pts, row_span):
        """Per-column ppc + nd tag, given a profile accumulated over row_span rows."""
        ppc = (pts / (max(1, row_span) * _K * _K)) * math.pi * _sr ** 2
        tags = [_cached_best_nd_tag(max(1.0, float(p))) for p in ppc]
        return tags

    def _batch_cost_at_tags(tags, ppc_arr):
        """Vectorized nd_choice.cost_at_array over a PER-COLUMN list of tags
        paired with a per-column ppc array -- confirmed via profiling this
        session that _fit_penalty's cost-weighted branch was calling
        cost_at_array once PER COLUMN with a freshly-allocated 1-element
        array each time (487k calls, 67s, for one 10-tile stress-test run),
        even though cost_at_array is already a fully vectorized function
        given an array of ppc values for one fixed tag. Since only a
        handful of distinct nd tags ever appear among a strip's columns,
        grouping column indices by their tag and making ONE batched
        cost_at_array call per distinct tag (instead of one call per column)
        gives numerically identical results at a fraction of the calls."""
        # Grouping-by-tag itself needs to be vectorized too, not just the
        # cost_at_array calls -- confirmed via profiling this session
        # (30-tile r=750 stress test) that the Python-level
        # dict.setdefault()/list.append() loop below became the dominant
        # cost once acc_cols grew large (18.4s tottime, 25M+ dict/list
        # calls), even after the cost_at_array batching fix. nd tags are
        # (int, bool) pairs; encode each as a single integer (bool is 0/1,
        # nd is a small signed int) so np.unique can group indices in one
        # vectorized C-level pass instead of a per-column Python loop. The
        # subsequent per-unique-tag loop only iterates over the HANDFUL of
        # distinct tags actually present (not the column count), same as
        # the original dict-based grouping's outer loop, just reached
        # without the expensive per-column inner bookkeeping.
        out = np.empty(len(tags), dtype=np.float64)
        # zip(*tags) unzips the (nd, bool) tuple list in one C-level pass --
        # confirmed via profiling this session that np.fromiter fed by a
        # Python generator expression was, counter-intuitively, SLOWER than
        # the original dict-based grouping it was meant to replace (20s
        # tottime from fromiter's per-element generator consumption
        # overhead alone). zip(*tags) avoids that entirely.
        _nd_tuple, _flag_tuple = zip(*tags)
        _nd_arr = np.array(_nd_tuple, dtype=np.int64)
        _flag_arr = np.array(_flag_tuple, dtype=np.int64)
        _encoded = _nd_arr * 2 + _flag_arr
        _uniq_enc, _inv = np.unique(_encoded, return_inverse=True)
        for _gi in range(len(_uniq_enc)):
            _mask = _inv == _gi
            _enc = int(_uniq_enc[_gi])
            _tag = (_enc // 2, bool(_enc % 2))
            out[_mask] = _nd_cost_at_array(_sr_bench_key, _tag, ppc_arr[_mask])
        return out

    def _fit_penalty(acc_cols, acc_pts, acc_row_span, cand_cols, cand_pts,
                      acc_src_cols=None, acc_src_pts=None,
                      cand_src_cols=None, cand_src_pts=None):
        """Fraction of a merge-joined column comparison that disagrees on
        mechanism -- 0 = perfect fit, 1 = totally different.
        (#1) Vectorized merge-join: scatter both profiles onto the shared
        union-of-columns index via searchsorted (numpy C-level), instead of
        building/probing Python dicts per column -- same comparison, same
        result, just without per-column Python-level dict overhead.

        config.USE_SOURCE_WEIGHTED_FIT_PENALTY (default off): acc_cols/
        cand_cols (and their pts) are TARGET-point derived -- they decide
        WHICH nd tag a column would get, but weighting the penalty by that
        same target mass means a column with lots of target points but no
        actual SOURCE points can dominate the merge decision, while a
        column where SOURCE points actually sit (and would pay the cost of
        a wrong nd) gets diluted away. When this flag is set and the
        acc_src_*/cand_src_* profiles are supplied, the comparison universe
        is restricted to columns with SOURCE points on either side, and the
        mass weighting used for the bad/total ratio switches to SOURCE
        point counts -- the target profiles are still used only to look up
        what nd tag each side would resolve to at that column."""
        _src_weighted = (acc_src_cols is not None and cand_src_cols is not None
                          and getattr(_cfg, 'USE_SOURCE_WEIGHTED_FIT_PENALTY', False))
        if _src_weighted:
            if len(acc_src_cols) == 0 and len(cand_src_cols) == 0:
                return 0.0
            all_cols = np.union1d(acc_src_cols, cand_src_cols)
            src_acc_full = np.zeros(len(all_cols), dtype=np.float64)
            if len(acc_src_cols):
                src_acc_full[np.searchsorted(all_cols, acc_src_cols)] = acc_src_pts
            src_cand_full = np.zeros(len(all_cols), dtype=np.float64)
            if len(cand_src_cols):
                src_cand_full[np.searchsorted(all_cols, cand_src_cols)] = cand_src_pts
            src_mass = src_acc_full + src_cand_full
            total_mass = float(src_mass.sum())
            if total_mass <= 0:
                return 0.0
            # target-derived counts at these SAME (source-populated) columns,
            # used only to resolve which nd tag each side would pick -- 0 if
            # a source-populated column happens to have no target points.
            acc_tgt_full = np.zeros(len(all_cols), dtype=np.float64)
            if len(acc_cols):
                _pos = np.searchsorted(all_cols, acc_cols)
                _in = (_pos < len(all_cols)) & (all_cols[np.clip(_pos, 0, len(all_cols) - 1)] == acc_cols)
                acc_tgt_full[_pos[_in]] = np.asarray(acc_pts)[_in]
            cand_tgt_full = np.zeros(len(all_cols), dtype=np.float64)
            if len(cand_cols):
                _pos = np.searchsorted(all_cols, cand_cols)
                _in = (_pos < len(all_cols)) & (all_cols[np.clip(_pos, 0, len(all_cols) - 1)] == cand_cols)
                cand_tgt_full[_pos[_in]] = np.asarray(cand_pts)[_in]
            acc_ppc = (acc_tgt_full / (max(1, acc_row_span) * _K * _K)) * math.pi * _sr ** 2
            cand_ppc = (cand_tgt_full / (_K * _K)) * math.pi * _sr ** 2
            acc_tgt_l = acc_tgt_full.tolist(); cand_tgt_l = cand_tgt_full.tolist()
            acc_ppc_l = acc_ppc.tolist(); cand_ppc_l = cand_ppc.tolist()
            mass_l = src_mass.tolist()
            src_acc_l = src_acc_full.tolist(); src_cand_l = src_cand_full.tolist()

            _use_cost_weighted = getattr(_cfg, 'USE_COST_WEIGHTED_FIT_PENALTY', False)
            if _use_cost_weighted:
                # Cost-weighted variant (config.USE_COST_WEIGHTED_FIT_PENALTY):
                # replaces the flat mass-weighted disagreement fraction with an
                # actual estimated-ms cost delta via nd_choice.cost_at_array.
                # For each disagreeing column: cost_separate = both sides at
                # their own individually-optimal tag (a_tag/b_tag, already the
                # per-side best); cost_merged = both sides at whichever ONE of
                # {a_tag, b_tag} minimizes their combined cost (what the
                # merged group would actually end up using -- reuses the
                # existing point-count-weighted cost model instead of assuming
                # either side "wins"). wasted = cost_merged - cost_separate,
                # always >= 0 since cost_separate already uses each side's own
                # best. Bookended by a floor (total cost too small to bother)
                # and a ceiling (absolute waste too large regardless of ratio)
                # -- see config.py docstring for why both guards exist.
                # Vectorized (see _batch_cost_at_tags docstring): same 4
                # cost quantities as the original per-column loop
                # (cost_a_at_a/cost_b_at_b/cost_a_at_b/cost_b_at_a), just
                # computed for ALL columns in a handful of batched
                # cost_at_array calls (grouped by distinct tag) instead of
                # one cost_at_array call per column. A column with zero
                # source mass on both sides naturally contributes 0 to both
                # totals (src_acc_full/src_cand_full are 0 there), matching
                # the original's explicit `continue` skip.
                _n_cols = len(all_cols)
                a_tags = [_cached_best_nd_tag(max(1.0, acc_ppc_l[i])) if acc_tgt_l[i] > 0 else (0, True)
                          for i in range(_n_cols)]
                b_tags = [_cached_best_nd_tag(max(1.0, cand_ppc_l[i])) if cand_tgt_l[i] > 0 else (0, True)
                          for i in range(_n_cols)]
                _a_ppc_arr = np.maximum(1.0, acc_ppc)
                _b_ppc_arr = np.maximum(1.0, cand_ppc)
                _cost_a_at_a = _batch_cost_at_tags(a_tags, _a_ppc_arr) * src_acc_full
                _cost_b_at_b = _batch_cost_at_tags(b_tags, _b_ppc_arr) * src_cand_full
                _cost_a_at_b = _batch_cost_at_tags(b_tags, _a_ppc_arr) * src_acc_full
                _cost_b_at_a = _batch_cost_at_tags(a_tags, _b_ppc_arr) * src_cand_full
                _cost_separate_arr = _cost_a_at_a + _cost_b_at_b
                _agree_arr = np.fromiter(
                    (_same_nd_mechanism(a_tags[i], b_tags[i]) for i in range(_n_cols)),
                    dtype=bool, count=_n_cols)
                _cost_merged_arr = np.minimum(_cost_a_at_a + _cost_b_at_a,   # both use a_tag
                                               _cost_a_at_b + _cost_b_at_b)  # both use b_tag
                _wasted_arr = np.where(_agree_arr, 0.0, np.maximum(0.0, _cost_merged_arr - _cost_separate_arr))
                _total_separate = float(_cost_separate_arr.sum())
                _total_wasted = float(_wasted_arr.sum())
                _total_merged = _total_separate + _total_wasted
                _floor_ms = getattr(_cfg, 'COST_WEIGHTED_PENALTY_FLOOR_MS', 5.0)
                _ceiling_ms = getattr(_cfg, 'COST_WEIGHTED_PENALTY_CEILING_MS', 500.0)
                if _total_merged < _floor_ms:
                    return -1.0  # always fits: overhead of a separate strip would dominate
                if _total_wasted > _ceiling_ms:
                    return 2.0   # never fits: absolute waste too large regardless of ratio
                if _total_separate <= 0:
                    return 0.0
                return _total_wasted / _total_separate

            bad_mass = 0.0
            for i in range(len(all_cols)):
                a_tag = _cached_best_nd_tag(max(1.0, acc_ppc_l[i])) if acc_tgt_l[i] > 0 else (0, True)
                b_tag = _cached_best_nd_tag(max(1.0, cand_ppc_l[i])) if cand_tgt_l[i] > 0 else (0, True)
                if not _same_nd_mechanism(a_tag, b_tag):
                    bad_mass += mass_l[i]
            return bad_mass / total_mass
        if len(acc_cols) == 0 or len(cand_cols) == 0:
            return 0.0
        all_cols = np.union1d(acc_cols, cand_cols)
        acc_full = np.zeros(len(all_cols), dtype=np.float64)
        acc_full[np.searchsorted(all_cols, acc_cols)] = acc_pts
        cand_full = np.zeros(len(all_cols), dtype=np.float64)
        cand_full[np.searchsorted(all_cols, cand_cols)] = cand_pts
        mass = acc_full + cand_full
        total_mass = float(mass.sum())
        if total_mass <= 0:
            return 0.0
        acc_ppc = (acc_full / (max(1, acc_row_span) * _K * _K)) * math.pi * _sr ** 2
        cand_ppc = (cand_full / (_K * _K)) * math.pi * _sr ** 2
        # .tolist() once up front -- repeated numpy scalar indexing inside a
        # Python loop has real per-access overhead; plain Python floats/lists
        # are cheaper to iterate. Same values, same comparisons, just cheaper
        # element access.
        acc_full_l = acc_full.tolist(); cand_full_l = cand_full.tolist()
        acc_ppc_l = acc_ppc.tolist(); cand_ppc_l = cand_ppc.tolist()
        mass_l = mass.tolist()
        bad_mass = 0.0
        for i in range(len(all_cols)):
            a_tag = _cached_best_nd_tag(max(1.0, acc_ppc_l[i])) if acc_full_l[i] > 0 else (0, True)
            b_tag = _cached_best_nd_tag(max(1.0, cand_ppc_l[i])) if cand_full_l[i] > 0 else (0, True)
            if not _same_nd_mechanism(a_tag, b_tag):
                bad_mass += mass_l[i]
        return bad_mass / total_mass

    def _band_from_profile(acc_cols, acc_pts, acc_row_span, src_cols_group, _dbg_label=None):
        """Column bands directly from the group's already-exact accumulated
        profile -- no separate, potentially-blended column density lookup.
        n_atomic = one band per occupied column (pre-merge); len(bands) =
        post-merge count once adjacent same-mechanism columns collapse.

        L3 check (added -- this was a real gap): the row-level grouping
        loop only checks memory against the group's BLENDED seed tag, which
        can be shallow/cheap even when one column band inside the group is
        genuinely deep/memory-expensive (that's the whole point of this
        planner detecting real per-column heterogeneity). Without a
        per-band check, a band could pass the group-level gate and still
        not actually fit L3. Uses REAL source point counts (src_cols_group,
        not acc_pts -- acc_pts is target-density-map derived, used only for
        the ppc/nd decision, not for memory sizing) via the same
        source-column-count approach the legacy planner's column split
        already uses."""
        if len(acc_cols) == 0:
            return [(None, None)], [0], []
        ppc = (acc_pts / (max(1, acc_row_span) * _K * _K)) * math.pi * _sr ** 2
        tags = [_cached_best_nd_tag(max(1.0, float(p))) for p in ppc]
        n_atomic = len(acc_cols)
        bands = []; run_start = 0
        for i in range(1, len(acc_cols)):
            if not _same_nd_mechanism(tags[run_start], tags[i]):
                bands.append((run_start, i - 1))
                run_start = i
        bands.append((run_start, len(acc_cols) - 1))

        def _band_src_count(_lo_col, _hi_col):
            if len(src_cols_group) == 0:
                return 0
            return int(np.count_nonzero((src_cols_group >= _lo_col) & (src_cols_group <= _hi_col)))

        # Re-split any band whose OWN memory footprint (real source points
        # in that column range, at that band's own nd tag) exceeds L3 --
        # all columns within a band already share one mechanism, so this is
        # a pure memory-driven split (greedy accumulation, cut on overflow),
        # no further heterogeneity check needed.
        _final_bands = []  # (start_idx, end_idx) into acc_cols, possibly finer than `bands`
        for (s, e) in bands:
            _lo_col = int(acc_cols[s]) * _K
            _hi_col = int(acc_cols[e]) * _K + _K - 1
            _n_src = _band_src_count(_lo_col, _hi_col)
            _mem_scale = _nd_mem_scale(tags[s])
            if _n_src * pt_bytes * _mem_scale <= remaining or s == e:
                _final_bands.append((s, e))
                continue
            _run_s = s; _run_n = 0
            for i in range(s, e + 1):
                _col_lo_i = int(acc_cols[i]) * _K
                _col_hi_i = int(acc_cols[i]) * _K + _K - 1
                _n_i = _band_src_count(_col_lo_i, _col_hi_i)
                if _run_n > 0 and (_run_n + _n_i) * pt_bytes * _mem_scale > remaining:
                    _final_bands.append((_run_s, i - 1))
                    _run_s = i; _run_n = _n_i
                else:
                    _run_n += _n_i
            _final_bands.append((_run_s, e))
        bands = _final_bands

        if _dbg_label is not None:
            print(f'  {_dbg_label} col-bands: n_atomic={n_atomic} -> n_merged={len(bands)}  '
                  f'per-band (ppc, nd): '
                  + ', '.join(f'({float(ppc[s]):.2f},{tags[s]})' for (s, e) in bands), flush=True)
        if len(bands) == 1:
            return [(None, None)], [tags[bands[0][0]][0]], [(float(ppc[bands[0][0]]), tags[bands[0][0]])]
        col_chunks = []; hints = []; band_info = []
        for (s, e) in bands:
            col_chunks.append((int(acc_cols[s]) * _K, int(acc_cols[e]) * _K + _K - 1))
            hints.append(tags[s][0])
            band_info.append((float(ppc[s]), tags[s]))
        return col_chunks, hints, band_info

    def _row_range_profile(cr_lo, cr_hi):
        """Sparse (occupied_columns, summed_pts) profile for a CONTIGUOUS
        coarse row range [cr_lo, cr_hi], aggregated by column in ONE range
        query -- mathematically equivalent to looping _row_profile +
        _merge_profiles over every row in the range one at a time, but
        computed in a single shot. Only valid to use when the whole range
        is already known to be mechanism-homogeneous (see the vectorized
        fast-path in _plan_chunks_clean) -- for a genuinely heterogeneous
        range this would blend distinct regions together, exactly the bug
        this whole planner exists to avoid."""
        if len(_ck_unique) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_ck_unique, key_hi, side='left'))
        if i1 <= i0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        cc_vals = _ck_unique[i0:i1] % _max_cc_coarse
        cnts = _n_pts_cc[i0:i1]
        uniq_cc, inv = np.unique(cc_vals, return_inverse=True)
        sums = np.zeros(len(uniq_cc), dtype=np.float64)
        np.add.at(sums, inv, cnts)
        return uniq_cc, sums

    def _row_range_profile_cluster(cr_lo, cr_hi, cluster_id):
        """Cluster-scoped variant of _row_range_profile -- see
        _row_profile_cluster's docstring for why (discards cells belonging
        to a different disjoint cluster before the fastpath's range-query
        merge, same efficiency-only rationale). Uses the per-group local
        cache (see _row_profile_cluster) instead of masking the global
        arrays on every call."""
        assert _cluster_cache_id[0] == cluster_id, 'cluster cache not refreshed for this group'
        _g_ck_unique, _g_n_pts_cc, _g_row_cr_unique, _g_row_starts, _g_row_ppc_mean_s, _g_row_ppc_max_s = _cluster_cache[0]
        if len(_g_ck_unique) == 0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        key_lo = cr_lo * _max_cc_coarse
        key_hi = (cr_hi + 1) * _max_cc_coarse
        i0 = int(np.searchsorted(_g_ck_unique, key_lo, side='left'))
        i1 = int(np.searchsorted(_g_ck_unique, key_hi, side='left'))
        if i1 <= i0:
            return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.float64)
        cc_vals = _g_ck_unique[i0:i1] % _max_cc_coarse
        cnts = _g_n_pts_cc[i0:i1]
        uniq_cc, inv = np.unique(cc_vals, return_inverse=True)
        sums = np.zeros(len(uniq_cc), dtype=np.float64)
        np.add.at(sums, inv, cnts)
        return uniq_cc, sums

    def _plan_chunks_clean():
        _dbg = getattr(_cfg, '_DEBUG_PRINT_CHUNK_STATS', False)
        _penalty_threshold = getattr(_cfg, 'CLEAN_PLANNER_PENALTY_THRESHOLD', 0.15)
        _lookahead = getattr(_cfg, 'CLEAN_PLANNER_LOOKAHEAD_ROWS', 2)
        # Scoped ONLY to the row-extension gate below (not _band_from_profile's
        # column-level L3 check, not the global L3 budget) -- see config.py
        # docstring on CLEAN_PLANNER_ROW_L3_BUDGET_MULT.
        _row_remaining = remaining * getattr(_cfg, 'CLEAN_PLANNER_ROW_L3_BUDGET_MULT', 1.0)
        # (Fix #1) Vectorized fast-path for long, genuinely homogeneous row
        # runs -- confirmed this session (15k, r=100) that when a dataset's
        # rows are uniform enough for the pre-filter to fast-accept every
        # single extension, the REMAINING cost is pure per-row Python loop
        # overhead (row_profile/merge_profiles/bookkeeping called once per
        # atomic row), not _fit_penalty itself. This precomputes, once and
        # fully vectorized, which consecutive atomic-row pairs are
        # PAIRWISE mechanism-identical on both mean and max ppc. A run of
        # consecutive pairwise-identical rows is, by transitivity, uniform
        # throughout -- the group's accumulated average can never drift
        # off that shared mechanism -- so it can be merged in ONE range
        # query (_row_range_profile) instead of looping row by row. Any
        # run shorter than 2 rows, or where pairwise agreement breaks, falls
        # through to the existing exact sequential loop unchanged -- this
        # is strictly an alternate, faster way to reach the SAME group
        # boundaries on the provably-safe subset, never a different decision.
        _use_fastpath = getattr(_cfg, 'USE_CLEAN_PLANNER_FASTPATH', False)
        _fast_run_end = None  # precomputed below, only if _use_fastpath
        if _use_fastpath and n_src_unique > 0 and _tgt_grp_range is None:
            # Non-disjoint path: unchanged, single global blended pass.
            _all_cr = (src_unique_rows // _K).astype(np.int64)
            _all_mean_ppc = np.zeros(n_src_unique, dtype=np.float64)
            _all_max_ppc = np.zeros(n_src_unique, dtype=np.float64)
            if len(_row_cr_unique):
                _pos = np.searchsorted(_row_cr_unique, _all_cr)
                _pos_clip = np.clip(_pos, 0, len(_row_cr_unique) - 1)
                _found = _row_cr_unique[_pos_clip] == _all_cr
                _all_mean_ppc[_found] = _row_ppc_mean_s[_pos_clip[_found]]
                _all_max_ppc[_found] = _row_ppc_max_s[_pos_clip[_found]]
            _all_mean_tags = [_cached_best_nd_tag(max(1.0, float(p))) for p in _all_mean_ppc]
            _all_max_tags = [_cached_best_nd_tag(max(1.0, float(p))) for p in _all_max_ppc]
            # _fast_run_end[i] = exclusive end index of the longest run of
            # pairwise mechanism-identical rows starting at i (>= i+1).
            _fast_run_end = np.arange(1, n_src_unique + 1, dtype=np.int64)
            for i in range(n_src_unique - 2, -1, -1):
                if (_same_nd_mechanism(_all_mean_tags[i], _all_mean_tags[i + 1])
                        and _same_nd_mechanism(_all_max_tags[i], _all_max_tags[i + 1])):
                    _fast_run_end[i] = _fast_run_end[i + 1]
        elif _use_fastpath and n_src_unique > 0:
            # Disjoint path: build _fast_run_end PER GROUP, using each
            # group's OWN density map (not the global blended
            # _row_ppc_mean_s/_row_ppc_max_s) -- confirmed this session
            # that the global-blend version was the same "sees more than
            # its own points" pattern as the source-density-map bug: a
            # row's blended ppc could be diluted by other groups sharing
            # its coarse row number, making genuine within-group
            # homogeneous runs look non-uniform and needlessly falling
            # through to the slow per-row loop. Atomic rows are already
            # contiguous per group (component_group_id is the primary sort
            # key), so this is one pass per group over a small local
            # segment, using the same _build_density_map_from_slice helper
            # as the per-group cache.
            _fast_run_end = np.arange(1, n_src_unique + 1, dtype=np.int64)
            # Full-length tag list, same role as the non-disjoint branch's
            # _all_mean_tags -- read by the fastpath's usage site (below,
            # in the main loop) to bound the batch-init by L3 budget using
            # its own (uniform) tag. Defaults to a cheap placeholder tag for
            # any row whose group has no target data at all (fastpath won't
            # be used for those anyway, since _fast_run_end stays 1 there).
            _all_mean_tags = [(0, True)] * n_src_unique
            _atomic_group_ids = component_group_id[src_row_bdry]
            _seg_bdry = np.flatnonzero(np.concatenate(
                [[True], _atomic_group_ids[1:] != _atomic_group_ids[:-1]]))
            _seg_ends = np.append(_seg_bdry[1:], n_src_unique)
            for _seg_i in range(len(_seg_bdry)):
                _s, _e = int(_seg_bdry[_seg_i]), int(_seg_ends[_seg_i])
                if _e - _s < 2:
                    continue
                _fp_gid = int(_atomic_group_ids[_s])
                _fp_gs, _fp_ge = _tgt_grp_range[_fp_gid]
                if _fp_gs < 0:
                    continue
                (_fp_ck_unique, _fp_n_pts_cc, _fp_ppc_cc, _fp_row_cr_unique, _fp_row_starts,
                 _fp_row_ppc_mean_s, _fp_row_ppc_max_s) = _build_density_map_from_slice(
                    _tgt_rows_grp_sorted[_fp_gs:_fp_ge], _tgt_cols_grp_sorted[_fp_gs:_fp_ge])
                if len(_fp_row_cr_unique) == 0:
                    continue
                _seg_cr = (src_unique_rows[_s:_e] // _K).astype(np.int64)
                _seg_mean_ppc = np.zeros(_e - _s, dtype=np.float64)
                _seg_max_ppc = np.zeros(_e - _s, dtype=np.float64)
                _pos = np.searchsorted(_fp_row_cr_unique, _seg_cr)
                _pos_clip = np.clip(_pos, 0, len(_fp_row_cr_unique) - 1)
                _found = _fp_row_cr_unique[_pos_clip] == _seg_cr
                _seg_mean_ppc[_found] = _fp_row_ppc_mean_s[_pos_clip[_found]]
                _seg_max_ppc[_found] = _fp_row_ppc_max_s[_pos_clip[_found]]
                _seg_mean_tags = [_cached_best_nd_tag(max(1.0, float(p))) for p in _seg_mean_ppc]
                _seg_max_tags = [_cached_best_nd_tag(max(1.0, float(p))) for p in _seg_max_ppc]
                _all_mean_tags[_s:_e] = _seg_mean_tags
                _seg_len = _e - _s
                _seg_fre = np.arange(1, _seg_len + 1, dtype=np.int64)
                for i in range(_seg_len - 2, -1, -1):
                    if (_same_nd_mechanism(_seg_mean_tags[i], _seg_mean_tags[i + 1])
                            and _same_nd_mechanism(_seg_max_tags[i], _seg_max_tags[i + 1])):
                        _seg_fre[i] = _seg_fre[i + 1]
                _fast_run_end[_s:_e] = _seg_fre + _s
        # EXPERIMENTAL, default off (config.USE_CLEAN_PLANNER_PREFILTER): skip
        # the exact _fit_penalty merge-join when a CHEAP blended check is
        # confident the row fits -- confirmed profiling this session showed
        # _fit_penalty is ~73% of the planner's own cost, called once per row-
        # extension attempt (far more often than _band_from_profile, called
        # once per finished group). Deliberately conservative: only fast-
        # accepts when BOTH the row's blended mean AND max ppc resolve to the
        # same mechanism as the group's own current mean/max -- using max
        # (not just mean) specifically because a mean-only check has the same
        # blind spot to small hot sub-regions found earlier this session
        # (the p90/max hot-spot investigation). Any mismatch on either
        # statistic falls through to the exact per-column comparison --
        # never fast-accepts on ambiguous signal, only fast-accepts a case
        # that's essentially guaranteed uniform.
        _use_prefilter = getattr(_cfg, 'USE_CLEAN_PLANNER_PREFILTER', False)
        _t0 = time.process_time() if _dbg else None
        _groups = []  # (row_chunk_idx, chunk_rows, col_chunks, col_hints, pt_s, pt_e)
        _n_row_merges = 0  # count of atomic rows absorbed into a larger group
        _n_prefilter_skips = 0  # count of _fit_penalty calls avoided via the pre-filter
        _idx = 0
        _group_i = 0
        while _idx < n_src_unique:
            _grp_t0 = time.process_time() if _dbg else None
            _pt_s = int(src_row_bdry[_idx])
            _cr0 = int(src_unique_rows[_idx]) // _K
            # Refresh the per-group local density-map cache: built fresh,
            # ONCE per group, directly from that group's own isolated
            # target-point slice via _build_density_map_from_slice (see its
            # docstring and _tgt_grp_range above) -- no global array is ever
            # touched, so cost is O(this group's own size), not O(total
            # dataset size). This replaced an earlier version that masked
            # the GLOBAL density map per group (O(total_size) per group,
            # confirmed via a 3/10/30-tile scaling check to be genuinely
            # quadratic in tile count: 9.96x and 8.14x total work for ~3x
            # more tiles -- the opposite of the "each disjoint cluster is
            # isolated" property this scoping is supposed to have).
            if component_group_id is not None and _tgt_grp_range is not None:
                _new_cluster_id = int(component_group_id[_pt_s])
                if _new_cluster_id != _cluster_cache_id[0]:
                    _cluster_cache_id[0] = _new_cluster_id
                    _gs, _ge = _tgt_grp_range[_new_cluster_id]
                    if _gs < 0:
                        _cluster_cache[0] = _build_density_map_from_slice(
                            np.empty(0, dtype=tgt_rows_full.dtype), np.empty(0, dtype=tgt_cols_full.dtype))
                    else:
                        (_g_ck_unique, _g_n_pts_cc, _g_ppc_cc, _g_row_cr_unique, _g_row_starts,
                         _g_row_ppc_mean_s, _g_row_ppc_max_s) = _build_density_map_from_slice(
                            _tgt_rows_grp_sorted[_gs:_ge], _tgt_cols_grp_sorted[_gs:_ge])
                        _cluster_cache[0] = (_g_ck_unique, _g_n_pts_cc, _g_row_cr_unique, _g_row_starts,
                                              _g_row_ppc_mean_s, _g_row_ppc_max_s)
                    if _src_grp_range is not None:
                        _ss, _se = _src_grp_range[_new_cluster_id]
                        if _ss < 0:
                            _cluster_cache_src[0] = _build_density_map_from_slice(
                                np.empty(0, dtype=rows.dtype), np.empty(0, dtype=cols.dtype))
                        else:
                            (_gs_ck_unique, _gs_n_pts_cc, _gs_ppc_cc, _gs_row_cr_unique, _gs_row_starts,
                             _gs_row_ppc_mean_s, _gs_row_ppc_max_s) = _build_density_map_from_slice(
                                rows[_ss:_se], cols[_ss:_se])
                            _cluster_cache_src[0] = (_gs_ck_unique, _gs_n_pts_cc, _gs_row_cr_unique, _gs_row_starts,
                                                      _gs_row_ppc_mean_s, _gs_row_ppc_max_s)
            # This group's own component-GROUP id (constant across the whole
            # planner group -- the hard break elsewhere guarantees a group
            # never spans two component groups), used to scope target
            # density lookups to just this group's own data
            # (config.USE_DISJOINT_COMPONENT_SCOPING). Deliberately the
            # MERGED group id, not the raw cluster id -- within one group,
            # the normal row+column planner is meant to see and handle
            # whatever internal heterogeneity exists (including different
            # raw clusters occupying different columns) exactly like it
            # would for any ordinary dataset.
            _cluster_id = int(component_group_id[_pt_s]) if component_group_id is not None else None
            if _dbg_same_nd_counter is not None:
                _dbg_same_nd_current_group[0] = _cluster_id if _cluster_id is not None else _group_i

            # Try the vectorized fast-path first: if a provably-homogeneous
            # multi-row run starts here (per _fast_run_end, precomputed
            # above), bound it by the L3 budget using its own (uniform, so
            # unambiguous) tag, and initialize the group from ONE range
            # query instead of looping row by row. Falls through to the
            # normal single-row init below whenever the fast run is <2 rows
            # or L3 doesn't allow taking it -- exact same eventual decision,
            # just reached without per-row Python overhead when safe to.
            _fast_h = 0
            if _fast_run_end is not None:
                _fr_end = int(_fast_run_end[_idx])
                if _fr_end - _idx >= 2:
                    _fr_tag = _all_mean_tags[_idx]
                    _fr_scale = _nd_mem_scale(_fr_tag)
                    _fr_pt_s = int(src_row_bdry[_idx])
                    _j = _idx + 1
                    while _j < _fr_end:
                        _cand_pt_e = int(src_row_bdry[_j + 1]) if _j + 1 < n_src_unique else n_pts
                        if (_cand_pt_e - _fr_pt_s) * pt_bytes * _fr_scale > _row_remaining:
                            break
                        _j += 1
                    _fast_h = _j - _idx
            if _dbg:
                print(f'  FASTPATH group#{_group_i} idx={_idx} fast_h={_fast_h} '
                      f'fast_run_end={int(_fast_run_end[_idx]) if _fast_run_end is not None else None}', flush=True)
            _src_weighted = getattr(_cfg, 'USE_SOURCE_WEIGHTED_FIT_PENALTY', False)
            if _fast_h >= 2:
                _cr_lo_fast = _cr0
                _cr_hi_fast = int(src_unique_rows[_idx + _fast_h - 1]) // _K
                acc_cols, acc_pts = (_row_range_profile_cluster(_cr_lo_fast, _cr_hi_fast, _cluster_id)
                                     if _cluster_id is not None
                                     else _row_range_profile(_cr_lo_fast, _cr_hi_fast))
                acc_span = _fast_h
                _h = _fast_h
                _n_row_merges += _fast_h - 1
                if _src_weighted:
                    acc_src_cols, acc_src_pts = (
                        _row_range_profile_src_cluster(_cr_lo_fast, _cr_hi_fast, _cluster_id)
                        if _cluster_id is not None
                        else _row_range_profile_src(_cr_lo_fast, _cr_hi_fast))
            else:
                acc_cols, acc_pts = (_row_profile_cluster(_cr0, _cluster_id)
                                     if _cluster_id is not None
                                     else _row_profile(_cr0))
                acc_span = 1
                _h = 1
                if _src_weighted:
                    acc_src_cols, acc_src_pts = (
                        _row_profile_src_cluster(_cr0, _cluster_id)
                        if _cluster_id is not None
                        else _row_profile_src(_cr0))
            if not _src_weighted:
                acc_src_cols, acc_src_pts = None, None
            # (#1) Running sum/max instead of re-scanning acc_pts from
            # scratch on every row-extension attempt -- confirmed this
            # session that a group growing over many rows was paying
            # O(occupied-cells-so-far) on EVERY single extension check just
            # for _seed_ppc/the prefilter's _acc_max_ppc. Same numbers,
            # updated incrementally instead of recomputed each time.
            _acc_sum = float(acc_pts.sum()) if len(acc_pts) else 0.0
            _acc_max = float(acc_pts.max()) if len(acc_pts) else 0.0
            # Lookahead/noise damping (mirrors the legacy planner's
            # _LOOKAHEAD_ROWS): a single row whose penalty exceeds the
            # threshold doesn't immediately cut -- it must be confirmed by
            # a consecutive run of _lookahead rows all showing high penalty
            # against the group's CURRENT (pre-pending-change) accumulated
            # profile, damping single-row noise the same way the legacy
            # planner's row-scan already does.
            _pending_run = 0
            _pending_snapshot = None  # (acc_cols, acc_pts, acc_span, idx_offset, acc_sum, acc_max, acc_src_cols, acc_src_pts) to roll back to
            _dbg_inner_iters = 0
            _dbg_inner_t0 = time.process_time() if _dbg else None
            if _dbg:
                print(f'  POST_FASTINIT group#{_group_i} fast_h={_fast_h} h={_h} '
                      f'len_acc_cols={len(acc_cols)}', flush=True)
            while _idx + _h < n_src_unique:
                if _dbg_inner_t0 is not None:
                    _dbg_inner_iters += 1
                    if _dbg_inner_iters % 200 == 0:
                        print(f'  INNER_PROGRESS group#{_group_i} idx={_idx} h={_h} '
                              f'iters={_dbg_inner_iters} elapsed={(time.process_time()-_dbg_inner_t0)*1000:.2f}ms',
                              flush=True)
                _pt_e = int(src_row_bdry[_idx + _h])
                # Hard break at component_group_id (the merged group), not
                # raw component_id. Corrected design: detect clusters, group
                # them, then run the SAME unmodified row+column planner
                # independently per group. Within a group, growth across a
                # raw cluster boundary is simply attempted like any ordinary
                # row transition -- _fit_penalty/_band_from_profile already
                # correctly detect "these columns don't match" regardless of
                # WHY they don't match (real heterogeneity or different
                # clusters), so no extra disjoint-specific protection is
                # needed here. This is safe against the earlier monotonicity
                # bug because the sort key is component_group_id ALONE (no
                # raw component_id in between) -- row values stay
                # monotonically increasing within one group.
                if component_group_id is not None and component_group_id[_pt_s] != component_group_id[_pt_e]:
                    if _dbg and _h == 1:
                        print(f'  BREAK_REASON group#{_group_i} idx={_idx} h={_h} cause=group_boundary', flush=True)
                    break
                # Raw-cluster transition WITHIN the same group: the global
                # prefilter stats (_row_ppc_mean_s/_row_ppc_max_s) are blended
                # across ALL columns and can't tell that two unrelated
                # clusters merely happen to look similarly sparse -- letting
                # the prefilter fast-accept here let acc_cols grow unboundedly
                # across spatially-unrelated clusters (confirmed via the
                # 10-tile stress test: h grew ~1:1 with iterations, each one
                # costing more). Force the real _fit_penalty/_band_from_profile
                # check (which IS column-aware) at these transitions; ordinary
                # same-cluster row transitions still use the fast prefilter.
                _force_exact_check = (component_id is not None
                                       and component_id[_pt_s] != component_id[_pt_e]
                                       and getattr(_cfg, '_DEBUG_FORCE_EXACT_AT_CLUSTER_BOUNDARY', True))
                _seed_ppc = (_acc_sum / (acc_span * _K * _K) * math.pi * _sr ** 2
                             if len(acc_pts) else 1.0)
                _seed_tag = _cached_best_nd_tag(max(1.0, _seed_ppc))
                if (_pt_e - _pt_s) * pt_bytes * _nd_mem_scale(_seed_tag) > _row_remaining:
                    if _dbg and _h == 1:
                        print(f'  BREAK_REASON group#{_group_i} idx={_idx} h={_h} cause=l3_memory '
                              f'need={(_pt_e - _pt_s) * pt_bytes * _nd_mem_scale(_seed_tag):.0f} '
                              f'avail={_row_remaining:.0f} seed_tag={_seed_tag} seed_ppc={_seed_ppc:.2f}', flush=True)
                    break
                _cr_n = int(src_unique_rows[_idx + _h]) // _K
                # Note: cluster-scoping and row-dilution aren't combined here
                # (dilution's neighbor-row lookups aren't cluster-aware) --
                # cluster-scoping takes priority when both are on.
                # Group-scoped (component_group_id), not raw-cluster-scoped:
                # the candidate's own group is always the SAME as the
                # accumulated group's (guaranteed by the hard break above),
                # so _cluster_id (already the group's own id) applies to
                # both sides consistently.
                if _cluster_id is not None:
                    cand_cols, cand_pts = _row_profile_cluster(_cr_n, _cluster_id)
                else:
                    cand_cols, cand_pts = (_row_profile_diluted(_cr_n)
                                            if getattr(_cfg, 'USE_ROW_DILUTED_PENALTY', False)
                                            else _row_profile(_cr_n))
                if not _src_weighted:
                    cand_src_cols, cand_src_pts = (None, None)
                elif _cluster_id is not None:
                    cand_src_cols, cand_src_pts = _row_profile_src_cluster(_cr_n, _cluster_id)
                else:
                    cand_src_cols, cand_src_pts = _row_profile_src(_cr_n)

                _fits = True
                _dbg_trace = getattr(_cfg, '_DEBUG_TRACE_FIT_PENALTY', False)
                _dbg_penalty_val = None
                _dbg_via = None
                if _use_prefilter and not _force_exact_check and len(cand_cols) > 0 and len(acc_cols) > 0:
                    # Group-scoped prefilter stats when disjoint scoping is
                    # on (from the per-group cache, refreshed above), not
                    # the global _row_ppc_mean_s/_row_ppc_max_s -- those
                    # blend every group's density together, which meant the
                    # "does this look confidently the same mechanism" check
                    # rarely succeeded even when the group's OWN density was
                    # genuinely uniform (confirmed this session: only ~4.5%
                    # of row-extension attempts hit the prefilter shortcut
                    # on a 30-tile stress test). A group's own mean/max ppc
                    # is exactly what its own row-extension decisions should
                    # be judged against.
                    if _cluster_id is not None:
                        _pf_row_cr_unique, _pf_row_ppc_mean_s, _pf_row_ppc_max_s = (
                            _cluster_cache[0][2], _cluster_cache[0][4], _cluster_cache[0][5])
                    else:
                        _pf_row_cr_unique, _pf_row_ppc_mean_s, _pf_row_ppc_max_s = (
                            _row_cr_unique, _row_ppc_mean_s, _row_ppc_max_s)
                    _cand_mean = _sparse_row_lookup(_cr_n, _pf_row_cr_unique, _pf_row_ppc_mean_s)
                    _cand_max = _sparse_row_lookup(_cr_n, _pf_row_cr_unique, _pf_row_ppc_max_s)
                    _acc_mean_ppc = _acc_sum / (acc_span * _K * _K) * math.pi * _sr ** 2
                    _acc_max_ppc = _acc_max / (_K * _K) * math.pi * _sr ** 2
                    if (_cand_mean > 0.0 and _cand_max > 0.0
                            and _same_nd_mechanism(_cached_best_nd_tag(max(1.0, _cand_mean)),
                                                    _cached_best_nd_tag(max(1.0, _acc_mean_ppc)))
                            and _same_nd_mechanism(_cached_best_nd_tag(max(1.0, _cand_max)),
                                                    _cached_best_nd_tag(max(1.0, _acc_max_ppc)))):
                        _n_prefilter_skips += 1
                        _dbg_via = 'prefilter_skip'
                    else:
                        _dbg_penalty_val = _fit_penalty(acc_cols, acc_pts, acc_span, cand_cols, cand_pts,
                                                         acc_src_cols, acc_src_pts, cand_src_cols, cand_src_pts)
                        _fits = _dbg_penalty_val <= _penalty_threshold
                        _dbg_via = 'fit_penalty'
                else:
                    _dbg_penalty_val = _fit_penalty(acc_cols, acc_pts, acc_span, cand_cols, cand_pts,
                                                     acc_src_cols, acc_src_pts, cand_src_cols, cand_src_pts)
                    _fits = _dbg_penalty_val <= _penalty_threshold
                    _dbg_via = 'fit_penalty'
                if _dbg_trace:
                    print(f'    FIT_TRACE group#{_group_i} idx={_idx} h={_h} acc_span={acc_span} '
                          f'cr_n={_cr_n} via={_dbg_via} penalty={_dbg_penalty_val} '
                          f'fits={_fits} pending_run={_pending_run}', flush=True)

                if not _fits:
                    # Plain lookahead damping, same as the non-disjoint path --
                    # no special-casing needed: within one component group,
                    # an ordinary density transition (whether from real
                    # heterogeneity or a raw cluster boundary) is handled
                    # identically, exactly like any other dataset.
                    if _pending_run == 0:
                        _pending_snapshot = (acc_cols, acc_pts, acc_span, _h, _acc_sum, _acc_max,
                                              acc_src_cols, acc_src_pts)
                    _pending_run += 1
                    if _pending_run >= _lookahead:
                        break  # confirmed real change: cut at the snapshot, not here
                    # not yet confirmed -- tentatively include this row and keep watching
                else:
                    _pending_run = 0
                    _pending_snapshot = None
                acc_cols, acc_pts = _merge_profiles(acc_cols, acc_pts, cand_cols, cand_pts)
                if _src_weighted:
                    acc_src_cols, acc_src_pts = _merge_profiles(
                        acc_src_cols, acc_src_pts, cand_src_cols, cand_src_pts)
                _acc_sum += float(cand_pts.sum()) if len(cand_pts) else 0.0
                if len(cand_pts):
                    _cand_max_val = float(cand_pts.max())
                    if _cand_max_val > _acc_max:
                        _acc_max = _cand_max_val
                acc_span += 1
                _h += 1
                _n_row_merges += 1
            if _pending_snapshot is not None:
                # Roll the group back to just before the confirmed change
                # started, so the row(s) tentatively absorbed during
                # lookahead don't get incorrectly folded into this group.
                (acc_cols, acc_pts, acc_span, _h, _acc_sum, _acc_max,
                 acc_src_cols, acc_src_pts) = _pending_snapshot
            _final_pt_e = int(src_row_bdry[_idx + _h]) if _idx + _h < n_src_unique else n_pts
            _lbl = f'group#{_group_i}' if _dbg else None
            _src_cols_group = cols[_pt_s:_final_pt_e]
            _col_chunks, _col_hints, _band_info = _band_from_profile(
                acc_cols, acc_pts, acc_span, _src_cols_group, _lbl)
            _groups.append((_idx, _h, _col_chunks, _col_hints, _pt_s, _final_pt_e))
            if _grp_t0 is not None:
                _grp_dt = (time.process_time() - _grp_t0) * 1000
                if _grp_dt > 50:  # only print groups that actually cost something, avoid flooding output
                    _grp_cgid = int(component_group_id[_pt_s]) if component_group_id is not None else -1
                    print(f'GROUP_TIME group#{_group_i} component_group_id={_grp_cgid} '
                          f'n_pts=[{_pt_s},{_final_pt_e})={_final_pt_e-_pt_s} h={_h} time={_grp_dt:.2f}ms', flush=True)
            _idx += _h
            _group_i += 1
        if _t0 is not None:
            print(f'CLEAN_PLANNER row+col grouping: {(time.process_time()-_t0)*1000:.2f}ms '
                  f'n_atomic_rows={n_src_unique} -> n_groups={len(_groups)} '
                  f'(merged {_n_row_merges} row-extensions, '
                  f'{_n_prefilter_skips} prefilter-skipped _fit_penalty calls)  '
                  f'n_chunks={sum(len(g[2]) for g in _groups)}', flush=True)
        if _dbg_same_nd_counter is not None:
            _total_calls = sum(_dbg_same_nd_counter.values())
            _sorted_groups = sorted(_dbg_same_nd_counter.items(), key=lambda kv: -kv[1])
            print(f'SAME_ND_CALLS total={_total_calls} n_groups_with_calls={len(_dbg_same_nd_counter)}', flush=True)
            for _gid, _cnt in _sorted_groups[:15]:
                print(f'  SAME_ND_BY_GROUP group_id={_gid} calls={_cnt} '
                      f'pct={100.0*_cnt/max(1,_total_calls):.2f}%', flush=True)

        _t1 = time.process_time() if getattr(_cfg, '_DEBUG_PRINT_CHUNK_STATS', False) else None
        _plans = []
        for (_rci, _sp_h, _col_chunks, _col_hints, _pt_s, _pt_e) in _groups:
            _cr_lo_strip = int(src_unique_rows[_rci]) // _K
            _cr_hi_strip = int(src_unique_rows[_rci + _sp_h - 1]) // _K
            _strip_cost = (_pt_e - _pt_s) * pt_bytes
            _dom_nd = max(_col_hints) if _col_hints else 0
            _sp_nd_tag = (_dom_nd, True)
            _plans.append((_rci, _sp_h, _sp_nd_tag, 1.0, _sp_nd_tag,
                           _col_chunks, _col_hints, _pt_s, _pt_e, _strip_cost, _cr_lo_strip, _cr_hi_strip))
        if _t1 is not None:
            print(f'CLEAN_PLANNER strip_plans assembly: {(time.process_time()-_t1)*1000:.2f}ms', flush=True)
            _heights = [g[1] for g in _plans]
            _nd_tags = [g[2] for g in _plans]
            if _heights:
                _h_arr = np.array(_heights)
                print(f'CLEAN_PLANNER strip heights: n={len(_h_arr)} '
                      f'min={_h_arr.min()} p50={int(np.percentile(_h_arr,50))} '
                      f'p90={int(np.percentile(_h_arr,90))} max={_h_arr.max()} '
                      f'mean={_h_arr.mean():.2f}', flush=True)
                from collections import Counter
                _nd_counts = Counter(_nd_tags)
                _nd_sorted = sorted(_nd_counts.items(), key=lambda kv: -kv[1])
                print(f'CLEAN_PLANNER nd_tag distribution (top 15 of {len(_nd_counts)}): '
                      f'{_nd_sorted[:15]}', flush=True)
        return _plans

    chunk_num = 0

    # DIAGNOSTIC ONLY (config._DEBUG_REPORT_DISJOINT_CLUSTERS, default off):
    # report disjoint-cloud cluster count/sizes and a simple greedy L3
    # bin-packing, BEFORE planning runs -- purely informational, feeds
    # nothing into the actual plan. See config.py docstring.
    if getattr(_cfg, '_DEBUG_REPORT_DISJOINT_CLUSTERS', False):
        _dcl_t0 = time.process_time()
        _dcl_x = point_xy[:, 0]
        _dcl_labels = _find_disjoint_clouds(_dcl_x, point_xy[:, 1], r)
        _dcl_uniq, _dcl_inv, _dcl_counts = np.unique(_dcl_labels, return_inverse=True, return_counts=True)
        # per-cluster x-bounds via minimum.at/maximum.at scatter over the label groups
        _dcl_xmin = np.full(len(_dcl_uniq), np.inf)
        _dcl_xmax = np.full(len(_dcl_uniq), -np.inf)
        np.minimum.at(_dcl_xmin, _dcl_inv, _dcl_x)
        np.maximum.at(_dcl_xmax, _dcl_inv, _dcl_x)
        _dcl_order = np.argsort(-_dcl_counts)  # descending by size, for the raw (pre-merge) report
        _dcl_sizes_desc = _dcl_counts[_dcl_order]
        print(f'DISJOINT_CLUSTERS n_clusters={len(_dcl_uniq)} n_src_pts={n_pts} '
              f'sizes(desc, top 20)={_dcl_sizes_desc[:20].tolist()}'
              f'{"..." if len(_dcl_sizes_desc) > 20 else ""}', flush=True)
        for _dcl_r in range(min(20, len(_dcl_order))):
            _dcl_oi = _dcl_order[_dcl_r]
            print(f'  cluster rank={_dcl_r} size={_dcl_counts[_dcl_oi]} '
                  f'x_min={_dcl_xmin[_dcl_oi]:.1f} x_max={_dcl_xmax[_dcl_oi]:.1f}', flush=True)
        # Simple point-count bin-packing (not the real nd_mem_scale cost
        # model): sort ASCENDING by size, then any cluster below 5% of the
        # L3 cap gets folded together with its next-smallest neighbor
        # (repeating until the running merged group clears that 5%
        # threshold) -- deliberately not pessimistic about the FULL L3 cap
        # either: a resulting group is allowed to end up over 100% of cap
        # (even up to ~110%+) without any attempt to avoid that overshoot.
        # Bounds are tracked per merged group too (min of mins, max of maxes
        # across every cluster folded into that group) -- ascending order
        # means a merged group's clusters are NOT spatially related (size-
        # adjacency, not position), so its x-bounds can span everything.
        _dcl_cap_pts = max(1, int(l3_budget / pt_bytes))
        _dcl_small_thresh = 0.05 * _dcl_cap_pts
        _dcl_asc_order = np.argsort(_dcl_counts)
        _dcl_sizes_asc = _dcl_counts[_dcl_asc_order]
        _dcl_xmin_asc = _dcl_xmin[_dcl_asc_order]
        _dcl_xmax_asc = _dcl_xmax[_dcl_asc_order]
        _dcl_groups = []  # (total_size, group_x_min, group_x_max)
        _dcl_i, _dcl_n = 0, len(_dcl_sizes_asc)
        while _dcl_i < _dcl_n:
            _dcl_cur = int(_dcl_sizes_asc[_dcl_i])
            _dcl_gxmin = float(_dcl_xmin_asc[_dcl_i])
            _dcl_gxmax = float(_dcl_xmax_asc[_dcl_i])
            _dcl_i += 1
            while _dcl_cur < _dcl_small_thresh and _dcl_i < _dcl_n:
                _dcl_cur += int(_dcl_sizes_asc[_dcl_i])
                _dcl_gxmin = min(_dcl_gxmin, float(_dcl_xmin_asc[_dcl_i]))
                _dcl_gxmax = max(_dcl_gxmax, float(_dcl_xmax_asc[_dcl_i]))
                _dcl_i += 1
            _dcl_groups.append((_dcl_cur, _dcl_gxmin, _dcl_gxmax))
        _dcl_groups.sort(key=lambda g: -g[0])  # descending by size for the report
        print(f'DISJOINT_CLUSTERS l3_merged_groups n_groups={len(_dcl_groups)} '
              f'cap_pts_per_group={_dcl_cap_pts} small_thresh(5%)={_dcl_small_thresh:.0f}', flush=True)
        for _dcl_r, (_dcl_sz, _dcl_gmin, _dcl_gmax) in enumerate(_dcl_groups[:20]):
            print(f'  merged_group rank={_dcl_r} size={_dcl_sz} '
                  f'x_min={_dcl_gmin:.1f} x_max={_dcl_gmax:.1f}', flush=True)
        if len(_dcl_groups) > 20:
            print(f'  ... and {len(_dcl_groups) - 20} more merged groups', flush=True)
        print(f'DISJOINT_CLUSTERS overhead={(time.process_time()-_dcl_t0)*1000:.2f}ms', flush=True)

    # Clean planner is now the only chunk planner (legacy row/column density-
    # blending scan removed).
    _strip_plans = _plan_chunks_clean()
    if _dbg_phase:
        print(f'DISJOINT_PHASE planning (_plan_chunks_clean, all groups): {(time.process_time()-_phase_t0)*1000:.2f}ms '
              f'n_strips={len(_strip_plans)} n_chunks={sum(len(_sp[5]) for _sp in _strip_plans)}', flush=True)
        _phase_t0 = time.process_time()
    if getattr(_cfg, '_DEBUG_PRINT_CHUNK_STATS', False):
        for _pp_i, _sp in enumerate(_strip_plans):
            print(f'CLEAN_STRIP#{_pp_i} row_chunk_idx={_sp[0]} h={_sp[1]} '
                  f'n_pts=[{_sp[7]},{_sp[8]})={_sp[8]-_sp[7]} nd_tag={_sp[2]} '
                  f'n_col_chunks={len(_sp[5])}', flush=True)
    # DIAGNOSTIC ONLY (config._DEBUG_PRINT_CHUNK_BBOX, default off): real
    # x/y bounding box of every final (strip x column-band) chunk, derived
    # from the ACTUAL points assigned to it (not grid-index math) -- lets a
    # plot compare each chunk's footprint against its merged group's own
    # spatial extent, to spot cases where a chunk's bounding box is much
    # wider than the points it actually contains (wasted execution).
    if getattr(_cfg, '_DEBUG_PRINT_CHUNK_BBOX', False):
        for _pp_i, _sp in enumerate(_strip_plans):
            _col_chunks_dbg, _pt_s_dbg, _pt_e_dbg = _sp[5], _sp[7], _sp[8]
            _strip_xy = point_xy[_pt_s_dbg:_pt_e_dbg]
            _strip_gid = int(component_group_id[_pt_s_dbg]) if component_group_id is not None else -1
            for _cc_i, (_cc_lo_dbg, _cc_hi_dbg) in enumerate(_col_chunks_dbg):
                if _cc_lo_dbg is None:
                    _chunk_xy = _strip_xy
                else:
                    _col_slice_dbg = cols[_pt_s_dbg:_pt_e_dbg]
                    _mask_dbg = (_col_slice_dbg >= _cc_lo_dbg) & (_col_slice_dbg <= _cc_hi_dbg)
                    _chunk_xy = _strip_xy[_mask_dbg]
                if len(_chunk_xy):
                    print(f'CHUNK_BBOX strip={_pp_i} chunk={_cc_i} group_id={_strip_gid} '
                          f'x=[{_chunk_xy[:,0].min():.1f},{_chunk_xy[:,0].max():.1f}] '
                          f'y=[{_chunk_xy[:,1].min():.1f},{_chunk_xy[:,1].max():.1f}] n={len(_chunk_xy)}',
                          flush=True)

    # Full plan known upfront now -- exact chunk count, no more running estimate.
    n_chunks_total = sum(len(_sp[5]) for _sp in _strip_plans)
    # Chunk-progress print cadence: update after every chunk if there are few,
    # otherwise cap it at ~20 updates total regardless of chunk count.
    _chunk_progress_step = max(1, n_chunks_total // 20)

    # EXPERIMENTAL (config.USE_FINAL_CHUNK_SORT / _DEBUG_PRINT_CHUNK_SORT_STATS,
    # both default off). Diagnostic-only pass so far -- computes, but does not
    # yet apply, a permutation that would reorder pts_source by FINAL chunk id
    # (not by disjoint component id -- see config.py docstring for why that
    # distinction matters). _strip_plans is already fully known at this point
    # (every point's (strip, col-band) membership is decided), so this is a
    # read-only derivation from existing planning output, not a new decision.
    # Two same-nd disjoint components sitting side-by-side in the SAME row
    # range (interleaved every row under the current row+snake sort, since
    # each row's snake order visits both) would show as a fragmented chunk_id
    # (many contiguous runs) here; sorting by chunk_id would collapse each
    # chunk down to exactly one run, at the cost of a one-time reorder cost
    # before the hot loop instead of scattered access during it.
    # Applies (not just diagnoses) a final-chunk-id sort -- see the long
    # discussion this session on why this is safe where sorting by raw
    # component id BEFORE planning was not: every point's (strip, col-band)
    # membership is already fully decided by _strip_plans at this point, so
    # reordering the working arrays now cannot corrupt any planning
    # decision (planning already finished). The clean planner is now the
    # only planner, so this only depends on USE_CLEAN_PLANNER_CHUNK_SORT
    # itself.
    _inv_perm_for_writeback = None
    _use_final_chunk_sort_apply = getattr(_cfg, 'USE_CLEAN_PLANNER_CHUNK_SORT', False)
    if _use_final_chunk_sort_apply or getattr(_cfg, 'USE_FINAL_CHUNK_SORT', False) \
            or getattr(_cfg, '_DEBUG_PRINT_CHUNK_SORT_STATS', False):
        _final_chunk_id = np.full(n_pts, -1, dtype=np.int64)
        _next_chunk_id = 0
        _chunk_bounds = []  # (pt_s, pt_e, ccl, cch) per (strip, band) -- reused below to rebuild _strip_plans
        for (_sp_rci, _sp_h, _sp_nd_tag, _sp_ppc, _sp_hot_tag,
             _sp_col_chunks, _sp_col_chunk_nd_hints, _sp_pt_s, _sp_pt_e,
             _sp_strip_cost, _sp_cr_lo_strip, _sp_cr_hi_strip) in _strip_plans:
            if _sp_pt_e <= _sp_pt_s:
                continue
            _strip_cols_slice = cols[_sp_pt_s:_sp_pt_e]
            for _band_i, (_ccl, _cch) in enumerate(_sp_col_chunks):
                if _ccl is None:
                    _band_mask = np.ones(_sp_pt_e - _sp_pt_s, dtype=bool)
                else:
                    _band_mask = (_strip_cols_slice >= _ccl) & (_strip_cols_slice <= _cch)
                _final_chunk_id[_sp_pt_s:_sp_pt_e][_band_mask] = _next_chunk_id
                _chunk_bounds.append((_sp_rci, _sp_h, _sp_col_chunk_nd_hints[_band_i], _sp_cr_lo_strip, _sp_cr_hi_strip))
                _next_chunk_id += 1
        # STRESS TEST ONLY (config._DEBUG_CHUNK_SORT_SHUFFLE_ORDER, default off):
        # relabels chunk ids into an evens-then-odds visiting order instead of
        # the natural ascending 0,1,2,... order, to verify the rebuild logic
        # doesn't secretly depend on ascending order -- any consistent
        # grouping-and-tracking order should be equally safe, since L3/
        # correctness were decided at planning time and don't depend on final
        # array position. _chunk_bounds is relabeled in lockstep so
        # _chunk_bounds[new_id] still corresponds to whichever chunk actually
        # got that new_id.
        if getattr(_cfg, '_DEBUG_CHUNK_SORT_SHUFFLE_ORDER', False) and _next_chunk_id > 0:
            _old_ids = np.arange(_next_chunk_id)
            _shuffle_order = np.concatenate([_old_ids[_old_ids % 2 == 0], _old_ids[_old_ids % 2 == 1]])
            _old_to_new = np.empty(_next_chunk_id, dtype=np.int64)
            _old_to_new[_shuffle_order] = np.arange(_next_chunk_id)
            _covered = _final_chunk_id >= 0
            _final_chunk_id[_covered] = _old_to_new[_final_chunk_id[_covered]]
            _chunk_bounds = [_chunk_bounds[i] for i in _shuffle_order.tolist()]

        _n_uncovered = int((_final_chunk_id < 0).sum())
        # runs = contiguous same-chunk_id stretches in CURRENT array order --
        # 1 run/chunk means already-contiguous (no benefit from reordering);
        # >1 run/chunk means scattered (reordering would help the hot loop).
        _run_bdry = np.empty(n_pts, dtype=bool)
        _run_bdry[0] = True
        _run_bdry[1:] = _final_chunk_id[1:] != _final_chunk_id[:-1]
        _n_runs = int(_run_bdry.sum())
        _perm = np.argsort(_final_chunk_id, kind='stable')
        if getattr(_cfg, '_DEBUG_PRINT_CHUNK_SORT_STATS', False):
            print(f'CHUNK_SORT_STATS n_pts={n_pts} n_chunks={_next_chunk_id} '
                  f'n_runs_before_sort={_n_runs} n_uncovered={_n_uncovered} '
                  f'fragmentation_ratio={_n_runs/max(_next_chunk_id,1):.2f}', flush=True)

        if _use_final_chunk_sort_apply and _next_chunk_id > 0 and _n_uncovered == 0:
            _cs_apply_t0 = time.process_time() if getattr(_cfg, '_DEBUG_PRINT_CHUNK_SORT_STATS', False) else None
            # Apply the reorder to every SOURCE-side per-point working array
            # (never pts_source itself, and never target-side arrays --
            # pts_vals_xy_full/tgt_*_full are independently sorted and
            # already safely decoupled per the self-search pattern).
            cols = cols[_perm]
            rows = rows[_perm]
            point_xy = point_xy[_perm]
            if point_offset is not None:
                point_offset = point_offset[_perm]
            cell_region = cell_region[_perm]
            region_and_trgl = region_and_trgl[_perm]
            # Gated purely by component_id being non-None (i.e. only when
            # USE_DISJOINT_COMPONENT_SCOPING is actually on) -- prepares
            # chunk-sort for eventual combination with disjoint-cluster
            # scoping (not yet implemented downstream). Without this,
            # component_id would go stale post-permutation the same way
            # contain_changed/overlap_changed did before the fix above --
            # not yet exercised by any real caller since the two flags
            # aren't combined anywhere yet, but needs to be correct before
            # they are.
            _cs_comp_t0 = time.process_time() if getattr(_cfg, '_DEBUG_PRINT_CHUNK_SORT_STATS', False) else None
            if component_id is not None:
                component_id = component_id[_perm]
            if _cs_comp_t0 is not None:
                print(f'CHUNK_SORT component_id reorder ({len(_perm)} pts): '
                      f'{(time.process_time()-_cs_comp_t0)*1000:.2f}ms '
                      f'(component_id={"present" if component_id is not None else "None -- no-op"})',
                      flush=True)
            # BUG FIX (found this session): contain_changed/overlap_changed and
            # their derived contain_starts_all/contain_ends_all/overlap_starts_all/
            # overlap_ends_all (built once near the top of this function, from the
            # ORIGINAL point order) were never recomputed after this permutation --
            # the main execution loop's cg_lo/cg_hi/og_lo/og_hi lookups then searched
            # stale, pre-sort group boundaries against the NEW point order, corrupting
            # results (confirmed: 14.4x-inflated sum on a real dataset). Re-derive
            # them here from the now-reordered arrays, identical formula to the
            # original construction.
            cell_changed[1:]    = (rows[1:] != rows[:-1]) | (cols[1:] != cols[:-1])
            if _single_region:
                contain_changed[1:] = cell_changed[1:]
                overlap_changed[1:] = cell_changed[1:]
            else:
                _region_trgl_changed_cs = region_and_trgl[1:] != region_and_trgl[:-1]
                contain_changed[1:] = (cell_changed[1:]
                                       | (cell_region[1:] // contain_region_mult != cell_region[:-1] // contain_region_mult)
                                       | _region_trgl_changed_cs)
                overlap_changed[1:] = (cell_changed[1:]
                                       | (cell_region[1:] % contain_region_mult != cell_region[:-1] % contain_region_mult)
                                       | _region_trgl_changed_cs)
            contain_starts_all = _np_flatnonzero(contain_changed)
            contain_ends_all   = _np_append(contain_starts_all[1:], n_pts)
            overlap_starts_all = _np_flatnonzero(overlap_changed)
            overlap_ends_all   = _np_append(overlap_starts_all[1:], n_pts)
            # src_row_bdry/src_unique_rows (built pre-sort, from the OLD row order)
            # are likewise stale for point-INDEX purposes -- the main loop below no
            # longer uses them for batch_pt_start/batch_pt_end (fixed to use the
            # already-correct, rebuilt _pt_s/_pt_e instead), but leaving them
            # unrecomputed means any OTHER future code relying on them post-sort
            # would hit the same class of bug. Flagging via a debug-only recompute
            # is deferred -- not needed for the current execution path, but worth
            # revisiting before combining chunk-sort with disjoint-component
            # scoping (component_id also isn't permuted above -- same fix needed
            # there before that combination is used).
            # Inverse permutation: pts_source itself is left in its ORIGINAL
            # row order (never reordered) -- results computed in the new,
            # chunk-sorted array position space get mapped back to that
            # original order at the final write-back step below.
            _inv_perm_for_writeback = np.empty_like(_perm)
            _inv_perm_for_writeback[_perm] = np.arange(len(_perm))
            # Rebuild _strip_plans: after sorting, each (strip, band) is
            # already exactly one contiguous slice -- no per-band column
            # masking needed downstream anymore (col_chunks=[(None,None)]),
            # and pt_s/pt_e become simple cumulative offsets into the
            # reordered array (matching the order _chunk_bounds was built in,
            # since np.argsort with kind='stable' preserves original
            # relative order within each chunk id).
            if _cs_apply_t0 is not None:
                print(f'CHUNK_SORT array reorder ({len(_perm)} pts x 6 arrays): '
                      f'{(time.process_time()-_cs_apply_t0)*1000:.2f}ms', flush=True)
                _cs_rebuild_t0 = time.process_time()
            # Chunk sizes via one bincount instead of one O(n_pts) mask-and-sum
            # PER CHUNK (was (_final_chunk_id == _bi).sum() in a Python loop
            # over _chunk_bounds -- O(n_chunks x n_pts), dominant when chunk
            # count is large e.g. on fragmented/pathological inputs).
            _chunk_sizes = np.bincount(_final_chunk_id, minlength=_next_chunk_id)
            _new_strip_plans = []
            _cum = 0
            for _bi, (_rci, _sp_h, _nd_hint, _cr_lo, _cr_hi) in enumerate(_chunk_bounds):
                _n_this = int(_chunk_sizes[_bi])
                _new_pt_s, _new_pt_e = _cum, _cum + _n_this
                _cum += _n_this
                _tag = (_nd_hint, True)
                _new_strip_plans.append((_rci, _sp_h, _tag, 1.0, _tag,
                                          [(None, None)], [_nd_hint],
                                          _new_pt_s, _new_pt_e, 0, _cr_lo, _cr_hi))
            _strip_plans = _new_strip_plans
            if _cs_apply_t0 is not None:
                print(f'CHUNK_SORT strip_plans rebuild ({len(_chunk_bounds)} chunks): '
                      f'{(time.process_time()-_cs_rebuild_t0)*1000:.2f}ms', flush=True)

    # ---- rasterize each chunk's (dilated) rectangle + nd onto a coarse K×K -----
    # block grid, then run Layer 1 aggregation with it (if run_aggregation), all
    # inline before executing this same call. Lets aggregation be driven by the
    # REAL, final (post-merge) chunk decision instead of an independently-
    # estimated one -- see this session's finding that re-estimating nd on a
    # single coarse block in isolation disagrees with the real (possibly much
    # larger, merged) chunk box's decision, since total_area_ppc/share_occupied
    # depend on the box actually evaluated. Previously this ran as a separate
    # plan_only=True call from disk_search.py, with aggregation sandwiched
    # between two search_and_aggregate calls -- folded into one call here so
    # nothing before this point (e.g. _parse_area_weight above) runs twice.
    # Margin/nd computation here intentionally mirrors the main loop's strip- and
    # chunk-level logic below (same _exact_chunk_nd / _margin_for_nd calls) --
    # duplicated rather than shared inline because threading this through the
    # ~700-line main loop (many branches: _TEST_RANDOM_ND, _do_weight, area_weight
    # variants, single vs multi-region) is higher-risk than a small, self-
    # contained pre-pass. Both copies call the same canonical functions, so they
    # cannot disagree with each other the way independently-implemented
    # estimates did.
    depth_by_block = {}
    needs_level0 = {}
    _dbg_chunk_nd_counter = {}  # TRUE per-column-chunk nd used for depth_by_block
    # Instrumentation-only (see TODO(perf) at the _chunk_nd > _nd_max clamp
    # below): counts how often a chunk's cost-optimal nd recommendation
    # exceeds the geometry's built depth and gets silently downgraded, vs
    # total chunks evaluated at that site -- measure the hit rate before
    # deciding whether the deep-build-once-and-downgrade strategy described
    # there is worth implementing.
    _dbg_nd_clamp_hits = 0
    _dbg_nd_clamp_total = 0
    # nd_needed -> cumulative target-point count (blk_n) of chunks that
    # wanted at least this deep an nd -- see _ensure_deep_nd's amortization
    # guard at the _chunk_nd > _nd_max site below.
    _dnd_pt_tally = {}
    # nd_needed -> running sum of (this chunk's actual _ppc_est * blk_n), for
    # computing a point-weighted mean of REAL observed ppc per bucket.
    _dnd_ppc_sum = {}
    for (_pci_rci, _pci_h, _pci_sp_nd_tag, _pci_sp_ppc, _pci_sp_hot_tag,
         _pci_col_chunks, _pci_col_chunk_nd_hints, _pci_pt_s, _pci_pt_e,
         _pci_strip_cost, _pci_cr_lo_strip, _pci_cr_hi_strip) in _strip_plans:
        if _pci_pt_e <= _pci_pt_s:
            continue
        _pci_chunk_src_rows = src_unique_rows[_pci_rci : _pci_rci + _pci_h]
        _pci_src_row_min = int(_pci_chunk_src_rows[0])
        _pci_src_row_max = int(_pci_chunk_src_rows[-1])
        _pci_strip_margin = r_rows
        if (getattr(_cfg, 'USE_CHEAP_COL_CHUNK_ND', False)
                and len(_pci_col_chunks) > getattr(_cfg, 'COL_CHUNK_ND_CHEAP_THRESHOLD', 20)):
            # Reuse the cheap hint the planner already computed for this
            # exact band instead of re-running the expensive weighted search --
            # see USE_CHEAP_COL_CHUNK_ND's docstring in config.py.
            _pci_nd_candidates = list(_pci_col_chunk_nd_hints)
        else:
            _pci_nd_candidates = [
                _exact_chunk_nd(_pci_cr_lo_strip, _pci_cr_hi_strip, _sccl, _scch)
                for (_sccl, _scch) in _pci_col_chunks
            ]
        for _nd_cand in _pci_nd_candidates:
            if _nd_cand < 0:
                _pci_strip_margin = max(_pci_strip_margin, _margin_for_nd(_nd_cand))
        _pci_need_row_lo = max(tgt_row_lo_global, _pci_src_row_min - _pci_strip_margin)
        _pci_need_row_hi = min(tgt_row_hi_global, _pci_src_row_max + _pci_strip_margin)
        for (_ccl, _cch), _pci_nd in zip(_pci_col_chunks, _pci_nd_candidates):
            if getattr(_cfg, '_DEBUG_PRINT_CHUNK_STATS', False):
                _dbg_chunk_nd_counter[_pci_nd] = _dbg_chunk_nd_counter.get(_pci_nd, 0) + 1
            _pci_margin = r_rows if _pci_nd >= 0 else _margin_for_nd(_pci_nd)
            if _ccl is not None:
                _pci_col_lo, _pci_col_hi = _ccl - _pci_margin, _cch + _pci_margin
            else:
                # No explicit column bounds (strip didn't need splitting) --
                # bound to THIS STRIP's own actual source-column footprint +
                # search margin, not the whole grid's column span. The old
                # fallback (0, full width) was harmless for a single roughly-
                # contiguous point cloud (own footprint ~= full width anyway)
                # but became catastrophic for well-separated point clusters:
                # an empty-of-nearby-data strip between two clusters still
                # iterated the ENTIRE column width in the block loop below,
                # scaling as O(n_empty_row_strips x full_column_span) -- 44M+
                # wasted dict lookups measured on a synthetic two-cluster
                # stress test (two real 15k-pt CBSA tiles ~7000km apart).
                # Same reasoning as the nd-heterogeneity branch above, which
                # already bounds to the strip's own source-column footprint
                # for exactly this reason.
                _strip_src_cols = cols[_pci_pt_s:_pci_pt_e]
                _pci_col_lo = int(_strip_src_cols.min()) - _pci_margin
                _pci_col_hi = int(_strip_src_cols.max()) + _pci_margin
            _blo, _bhi = _pci_need_row_lo // _K, _pci_need_row_hi // _K
            _clo, _chi = _pci_col_lo // _K, _pci_col_hi // _K
            # Populate depth_by_block/needs_level0 only for coarse blocks that
            # are ACTUALLY occupied (target points present) within [_blo,_bhi]
            # x [_clo,_chi], via the sparse coarse map (_ck_unique) already
            # built above -- not a dense range loop over every (row,col) pair
            # in the rectangle. Downstream consumers (point_assignment.py
            # aggregate_point_data_to_cells_adaptive_nd) read these dicts via
            # `.get(key, default)` with safe fallbacks, so unpopulated blocks
            # (empty of data) are handled correctly for free; nothing needs
            # an explicit entry. The strip's own row/col range can legitimately
            # span a huge, mostly-empty rectangle for a low-density/supercell
            # strip that got merged across a large gap between separated point
            # clusters -- a dense double loop over that rectangle blew up to
            # tens of millions of iterations on a two-cluster stress test
            # (two real 15k-pt CBSA tiles ~7000km apart), even though only a
            # tiny fraction of it was ever real, occupied data.
            if len(_ck_unique):
                _key_lo = int(_blo) * _max_cc_coarse
                _key_hi = (int(_bhi) + 1) * _max_cc_coarse
                _i0 = int(np.searchsorted(_ck_unique, _key_lo, side='left'))
                _i1 = int(np.searchsorted(_ck_unique, _key_hi, side='left'))
                _occ_keys = _ck_unique[_i0:_i1]
                _occ_cc = _occ_keys % _max_cc_coarse
                _occ_mask = (_occ_cc >= _clo) & (_occ_cc <= _chi)
                _occ_keys = _occ_keys[_occ_mask]
                _occ_cc = _occ_cc[_occ_mask]
                _occ_cr = _occ_keys // _max_cc_coarse
                for _br, _bc in zip(_occ_cr.tolist(), _occ_cc.tolist()):
                    _key = (_br, _bc)
                    depth_by_block[_key] = max(depth_by_block.get(_key, 0), max(0, _pci_nd))
                    needs_level0[_key] = needs_level0.get(_key, False) or (_pci_nd >= 0)

    import os as _diag_os3
    _DIAG_SINGLE_CALL = _diag_os3.environ.get('AABPL_DIAG_SINGLE_CALL')
    if _DIAG_SINGLE_CALL:
        print(f'DIAGTMP pre-agg tgt_rows_full[:5]={tgt_rows_full[:5].tolist()} '
              f'tgt_cols_full[:5]={tgt_cols_full[:5].tolist()} '
              f'sum_tgt_rows={int(tgt_rows_full.sum())} sum_tgt_cols={int(tgt_cols_full.sum())} '
              f'pts_vals_sum={float(pts_vals_xy_full[:, :n_c].sum())}', flush=True)

    if getattr(_cfg, '_DEBUG_PRINT_CHUNK_STATS', False):
        _depths = list(depth_by_block.values())
        _n_deep = sum(1 for _d in _depths if _d >= 0)
        _n_lvl0 = sum(1 for _v in needs_level0.values() if _v)
        from collections import Counter as _DebugCounter
        _depth_hist = dict(sorted(_DebugCounter(_depths).items()))
        _chunk_nd_hist = dict(sorted(_dbg_chunk_nd_counter.items()))
        print(f'DEBUGDEPTH n_blocks={len(depth_by_block)} n_blocks_nd_ge0={_n_deep} '
              f'n_needs_level0={_n_lvl0} max_depth={max(_depths) if _depths else None} '
              f'mean_depth={sum(_depths)/max(len(_depths),1):.3f} depth_hist={_depth_hist} '
              f'TRUE_chunk_nd_hist={_chunk_nd_hist}', flush=True)

    if plan_only:
        return depth_by_block, needs_level0

    if run_aggregation and getattr(_cfg, 'USE_ADAPTIVE_ND_AGGREGATION', False):
        from aabpl.search.point_assignment import aggregate_point_data_to_cells_adaptive_nd
        aggregate_point_data_to_cells_adaptive_nd(
            grid=grid, pts=pts_target, y=y, x=x, c=c,
            row_name=row_name, col_name=col_name,
            nest_depth=nest_depth, sr=_sr, silent=silent,
            depth_by_block=depth_by_block, needs_level0=needs_level0,
        )
        if _DIAG_SINGLE_CALL:
            print(f'DIAGTMP post-agg tgt_rows_full[:5]={tgt_rows_full[:5].tolist()} '
                  f'tgt_cols_full[:5]={tgt_cols_full[:5].tolist()} '
                  f'sum_tgt_rows={int(tgt_rows_full.sum())} sum_tgt_cols={int(tgt_cols_full.sum())} '
                  f'pts_vals_sum={float(pts_vals_xy_full[:, :n_c].sum())} '
                  f'sums_array_shape={grid.sums_array.shape} '
                  f'sums_array_sum={float(grid.sums_array.sum())} '
                  f'id_to_sums_by_lvl_len={len(grid._search_internals.id_to_sums_by_lvl or {})}',
                  flush=True)

    # ---- precomputed sums array (from aggregate_point_data_to_cells) -----------
    # Contain path: instead of summing block_cell_sums in a Python loop, index
    # directly into the contiguous _sums_array using codec keys → one numpy call.
    _sums_array  = grid.sums_array                           # (n_nodes, n_c) float64

    # Build sorted key/index arrays once for vectorised lookup in _contain_sum,
    # then cache them on the grid (not just the source dict) -- callers that
    # invoke search_and_aggregate more than once on the same grid (e.g.
    # detect_cluster_pts's null-distribution passes) need these arrays on
    # every call, but id_to_sums_by_lvl itself is freed after first use (see
    # below), so a naive "build from the dict every call" breaks on call two.
    _ck_keys = getattr(grid._search_internals, '_ck_keys_cache', None)
    _ck_idxs = getattr(grid._search_internals, '_ck_idxs_cache', None)
    if _ck_keys is None:
        _id_to_lvl = grid._search_internals.id_to_sums_by_lvl  # codec_int -> row index
        _ck_keys = np.array(sorted(_id_to_lvl.keys()), dtype=np.int64)
        _ck_idxs = np.array([_id_to_lvl[k] for k in _ck_keys], dtype=np.int64)
        grid._search_internals._ck_keys_cache = _ck_keys
        grid._search_internals._ck_idxs_cache = _ck_idxs
        # id_to_sums_by_lvl (the dict _id_to_lvl points at) is never read again
        # past this point -- _ck_keys/_ck_idxs (now cached above) are what
        # _contain_sum actually uses from here on, on this and future calls.
        # A dict entry costs ~6-9x a sorted-int64-array slot (boxed key +
        # hash table overhead vs 16 bytes), and at deep nest_depth this dict
        # can hold well over a million entries -- drop it rather than let it
        # sit alive for the rest of the Grid's lifetime doing nothing.
        grid._search_internals.id_to_sums_by_lvl = None
        del _id_to_lvl

    def _contain_sum(codec_offsets, home_codec_key):
        """Sum precomputed cell sums for a set of contain cells."""
        if n_c == 0 or len(codec_offsets) == 0:
            return zero_sum
        keys = codec_offsets + home_codec_key
        pos  = np.searchsorted(_ck_keys, keys)
        safe_pos = np.minimum(pos, len(_ck_keys) - 1)
        mask = (pos < len(_ck_keys)) & (_ck_keys[safe_pos] == keys)
        if not mask.any():
            return zero_sum
        return _sums_array[_ck_idxs[pos[mask]]].sum(axis=0)

    # ---- supercell region pre-scan: which nd<0 tags are used where ---------------
    # Re-derives each planned chunk's nd via the same _best_nd_tag(_sr, ppc_est)
    # call the main loop makes (cheap -- already proven so, it's what the main
    # loop itself does per chunk), purely to let _ensure_super_cell_target_index
    # restrict its index to the chunk regions that actually use each nd (dilated),
    # instead of indexing the whole target grid. Skipped for _TEST_RANDOM_ND
    # (that mode picks nd randomly at chunk-execution time, so a pre-scan via
    # _best_nd_tag wouldn't match -- _nd_chunk_regions stays empty, and
    # _ensure_super_cell_target_index falls back to its old, safe, full-grid
    # behaviour for every nd in that mode).
    _nd_chunk_regions = {}  # nd (nd<0 only) -> list of (row_lo, row_hi, col_lo, col_hi), fine-cell units
    if not _TEST_RANDOM_ND:
        for (_rci2, _sp_h2, _sp_nd_tag2, _sp_ppc2, _sp_hot_tag2,
             _col_chunks2, _col_chunk_nd_hints2, _pt_s2, _pt_e2, _strip_cost2, _cr_lo2, _cr_hi2) in _strip_plans:
            if _pt_e2 <= _pt_s2:
                continue
            _strip_cols2 = cols[_pt_s2:_pt_e2]
            _strip_rows2 = rows[_pt_s2:_pt_e2]
            for (_cc_lo2, _cc_hi2) in _col_chunks2:
                if _cc_lo2 is not None:
                    _bmask2 = (_strip_cols2 >= _cc_lo2) & (_strip_cols2 <= _cc_hi2)
                    if not _bmask2.any():
                        continue
                    _b_rows2 = _strip_rows2[_bmask2]; _b_cols2 = _strip_cols2[_bmask2]
                    _reg_nz2 = _sparse_box_ppc(_cr_lo2, _cr_hi2, _cc_lo2 // _K, _cc_hi2 // _K)
                else:
                    _b_rows2 = _strip_rows2; _b_cols2 = _strip_cols2
                    _reg_nz2 = _sparse_box_ppc(_cr_lo2, _cr_hi2)
                _nd2 = _exact_chunk_nd(_cr_lo2, _cr_hi2, _cc_lo2, _cc_hi2)
                if _nd2 < 0:
                    _rlo2, _rhi2 = int(_b_rows2.min()), int(_b_rows2.max())
                    _clo2, _chi2 = int(_b_cols2.min()), int(_b_cols2.max())
                    _nd_chunk_regions.setdefault(_nd2, []).append((_rlo2, _rhi2, _clo2, _chi2))

    # ---- batch-precompute cg/og lookups for ALL strips at once -----------------
    # (Post-planning bundling, step 1.) batch_pt_start/batch_pt_end (== _pt_s/
    # _pt_e) are already fully known for every strip once _strip_plans exists --
    # nothing in this lookup depends on anything computed inside the loop body
    # (unlike need_row_lo/hi, which depend on _strip_margin/_exact_chunk_nd and
    # aren't batched here -- riskier to vectorize, left as-is for now). Doing
    # ONE vectorized searchsorted call over ALL strips' boundaries instead of
    # 4 separate scalar searchsorted calls PER strip collapses what used to be
    # O(n_chunks) individual Python-level numpy calls into O(1) batched ones --
    # real savings for workloads with many small chunks (e.g. many small
    # disjoint clusters), zero change to what each strip actually computes.
    if len(_strip_plans):
        _all_pt_s = np.fromiter((_sp[7] for _sp in _strip_plans), dtype=np.int64, count=len(_strip_plans))
        _all_pt_e = np.fromiter((_sp[8] for _sp in _strip_plans), dtype=np.int64, count=len(_strip_plans))
        _cg_lo_all = _np_searchsorted(contain_starts_all, _all_pt_s, side='left')
        _cg_hi_all = _np_searchsorted(contain_starts_all, _all_pt_e, side='left')
        _og_lo_all = _np_searchsorted(overlap_starts_all, _all_pt_s, side='left')
        _og_hi_all = _np_searchsorted(overlap_starts_all, _all_pt_e, side='left')
        # Suffix sum of source points from each strip onward -- cheap proxy for
        # "how much work is left to amortize a deep-nd geometry build cost
        # over" (see _ensure_deep_nd / config.DEEP_ND_MIN_REMAINING_SRC_PTS).
        _strip_pt_counts = (_all_pt_e - _all_pt_s).astype(np.int64)
        _remaining_src_pts_from = np.cumsum(_strip_pt_counts[::-1])[::-1]
    else:
        _cg_lo_all = _cg_hi_all = _og_lo_all = _og_hi_all = np.empty(0, dtype=np.int64)
        _remaining_src_pts_from = np.empty(0, dtype=np.int64)

    # ---- main chunk loop --------------------------------------------------------
    # Consumes the precomputed _strip_plans instead of recomputing col-split
    # decisions inline -- everything below is otherwise unchanged from before
    # the planning pre-pass was hoisted out.
    row_iter = 0  # tracks even/odd for snake column order (kept for readability;
                  # _strip_plans was already built with matching parity above)
    for _strip_i, (row_chunk_idx, chunk_rows, _sp_nd_tag, _sp_ppc, _sp_hot_tag,
         _col_chunks, _col_chunk_nd_hints, _pt_s, _pt_e, _strip_cost, _cr_lo_strip, _cr_hi_strip) in enumerate(_strip_plans):
        _reverse = (row_iter % 2 == 1)

        chunk_src_rows = src_unique_rows[row_chunk_idx : row_chunk_idx + chunk_rows]
        src_row_min    = int(chunk_src_rows[0])
        src_row_max    = int(chunk_src_rows[-1])

        # Row-band margin: widened beyond r_rows to cover the deepest ACTUAL nd
        # among this strip's own column-chunks -- re-decided here with the same
        # exact density estimate the non-weighted dispatch branch uses (not the
        # planning-time hint, which can disagree with it near a crossover
        # threshold -- see the col-chunk loop below for the same fix applied
        # there). The row-band window covers the whole strip regardless of
        # which column-chunk needs the extra reach, so it must use the worst
        # case among all of them.
        _strip_margin = r_rows
        _strip_nd_candidates = [_exact_chunk_nd(_cr_lo_strip, _cr_hi_strip, _scc_lo, _scc_hi)
                                 for (_scc_lo, _scc_hi) in _col_chunks]
        for _nd_cand in _strip_nd_candidates:
            if _nd_cand < 0:
                _strip_margin = max(_strip_margin, _margin_for_nd(_nd_cand))

        # Cluster-scoped target gather (config.USE_DISJOINT_COMPONENT_SCOPING):
        # gather directly from this strip's OWN group's target points
        # (_tgt_rows_grp_sorted/_tgt_cols_grp_sorted/etc, sliced to
        # _tgt_grp_range[gid]) instead of the globally row-sorted
        # tgt_rows_full/tgt_cols_full followed by a component_group_id mask.
        # Previously: blk_lo:blk_hi was a ROW-range slice of the GLOBAL
        # arrays -- when multiple disjoint groups share the same row range
        # (e.g. two groups separated primarily by column, not row -- see the
        # 10-tile stress test), that slice pulled in every other group's
        # target points in the row band too, discarded only afterward by a
        # _tgt_cluster_mask_blk mask. Correctness was never at risk, but this
        # wasted-gather cost scaled with chunk count, which is what made
        # real-data small-r cases (many small merged groups) slower in
        # execution+finalize than non-disjoint despite identical results
        # (confirmed via 521k CBSA 16980 r=100 benchmark this session).
        _strip_cluster_id = int(component_group_id[_pt_s]) if component_group_id is not None else None
        if _strip_cluster_id is not None and _tgt_grp_range is not None:
            _tgt_g_lo, _tgt_g_hi = (int(_tgt_grp_range[_strip_cluster_id, 0]),
                                     int(_tgt_grp_range[_strip_cluster_id, 1]))
            _tgt_rows_src = _tgt_rows_grp_sorted
            _tgt_cols_src = _tgt_cols_grp_sorted
            _tgt_vals_src = _pts_vals_xy_grp_sorted
            _tgt_scnr_src = _tgt_scnr_grp_sorted
            _grp_row_lo_global = int(_tgt_rows_src[_tgt_g_lo]) if _tgt_g_hi > _tgt_g_lo else tgt_row_lo_global
            _grp_row_hi_global = int(_tgt_rows_src[_tgt_g_hi - 1]) if _tgt_g_hi > _tgt_g_lo else tgt_row_hi_global
        else:
            _tgt_g_lo, _tgt_g_hi = 0, n_tgt
            _tgt_rows_src = tgt_rows_full
            _tgt_cols_src = tgt_cols_full
            _tgt_vals_src = pts_vals_xy_full
            _tgt_scnr_src = tgt_scnr_full
            _grp_row_lo_global = tgt_row_lo_global
            _grp_row_hi_global = tgt_row_hi_global

        # target row band needed for this chunk
        need_row_lo = max(_grp_row_lo_global, src_row_min - _strip_margin)
        need_row_hi = min(_grp_row_hi_global, src_row_max + _strip_margin)

        # advance blk_lo / blk_hi to cover [need_row_lo, need_row_hi] -- must
        # also reset (not just "advance") whenever the active group changes,
        # since two different groups' row bands can coincidentally share the
        # same need_row_lo/need_row_hi values while pointing at different
        # underlying arrays/offsets.
        if (loaded_group_id != _strip_cluster_id or loaded_row_lo is None
                or need_row_lo != loaded_row_lo):
            blk_lo = _tgt_g_lo + int(_np_searchsorted(
                _tgt_rows_src[_tgt_g_lo:_tgt_g_hi], need_row_lo, side='left'))
        if (loaded_group_id != _strip_cluster_id or loaded_row_hi is None
                or need_row_hi != loaded_row_hi):
            blk_hi = _tgt_g_lo + int(_np_searchsorted(
                _tgt_rows_src[_tgt_g_lo:_tgt_g_hi], need_row_hi + 1, side='left'))
        loaded_row_lo = need_row_lo
        loaded_row_hi = need_row_hi
        loaded_group_id = _strip_cluster_id

        # ---- source point range and group slices for this row chunk -------------
        # Uses _pt_s/_pt_e (already unpacked from _strip_plans) directly instead of
        # recomputing via src_row_bdry[row_chunk_idx] -- identical value when chunk-
        # sort is off (same formula, computed once during planning), but src_row_bdry
        # is stale (built pre-sort) once chunk-sort has reordered the point arrays,
        # while _pt_s/_pt_e get correctly rebuilt to the new positions (see the
        # chunk-sort block above). Using the stale value there was the actual bug.
        batch_pt_start = _pt_s
        batch_pt_end   = _pt_e

        # Looked up from the batch-precomputed arrays (see above the loop)
        # instead of 4 fresh scalar searchsorted calls per strip.
        cg_lo    = int(_cg_lo_all[_strip_i])
        cg_hi    = int(_cg_hi_all[_strip_i])
        c_starts = contain_starts_all[cg_lo:cg_hi]
        c_ends   = contain_ends_all[cg_lo:cg_hi]

        og_lo    = int(_og_lo_all[_strip_i])
        og_hi    = int(_og_hi_all[_strip_i])
        o_starts = overlap_starts_all[og_lo:og_hi]
        o_ends   = overlap_ends_all[og_lo:og_hi]

        # coarse row range for this strip (used inside col-chunk loop)
        _cr_lo = _cr_lo_strip
        _cr_hi = _cr_hi_strip

        # ---- col chunk inner loop -----------------------------------------------
        for (_cc_lo, _cc_hi), _cc_nd_hint in zip(_col_chunks, _col_chunk_nd_hints):
            if _cc_lo is not None:
                # Re-decide this chunk's nd NOW, before slicing, using the exact
                # same density estimate + best_nd_tag call the non-weighted
                # branch below makes -- the planning-time hint (_cc_nd_hint)
                # was computed independently and can disagree at density values
                # near a crossover threshold (confirmed via brute-force
                # validation: e.g. ppc~413 classified nd=0 by one estimate and
                # nd=-1 by the other for the same chunk), silently leaving the
                # margin unwidened for chunks that actually need it. This
                # duplicates a cheap lookup (not the full weighted-decision
                # path) purely to size the gather window correctly; the
                # downstream ppc-estimate block still runs its own decision
                # unchanged for the actual dispatch.
                _cc_nd_actual = _exact_chunk_nd(_cr_lo, _cr_hi, _cc_lo, _cc_hi)
                # target: include this chunk's own margin either side, widened
                # beyond r_rows only if its actual (re-decided) nd is a
                # super-cell -- other, denser column-chunks in the same strip
                # keep the cheap native margin instead of all paying for this
                # one chunk's wider reach.
                _cc_margin = r_rows if _cc_nd_actual >= 0 else _margin_for_nd(_cc_nd_actual)
                tgt_col_lo       = _cc_lo - _cc_margin
                tgt_col_hi       = _cc_hi + _cc_margin
                tgt_cols_in_band = _tgt_cols_src[blk_lo:blk_hi]
                _col_mask = (tgt_cols_in_band >= tgt_col_lo) & (tgt_cols_in_band <= tgt_col_hi)
                col_filter_idx   = _np_flatnonzero(_col_mask)
                blk_rows = _tgt_rows_src[blk_lo:blk_hi][col_filter_idx]
                blk_cols = tgt_cols_in_band[col_filter_idx]
                blk_pts  = _tgt_vals_src[blk_lo:blk_hi][col_filter_idx]
                blk_scnr = _tgt_scnr_src[blk_lo:blk_hi][col_filter_idx]
                blk_n    = len(col_filter_idx)
                # restrict contain/overlap groups to this source col range.
                # Groups are sorted by (row, col) not col alone, so boolean masking
                # is required — searchsorted on cols would fail for chunk_rows > 1
                # because the same col can recur in a later row.
                if len(c_starts):
                    _c_mask       = (cols[c_starts] >= _cc_lo) & (cols[c_starts] <= _cc_hi)
                    _c_starts_col = c_starts[_c_mask]
                    _c_ends_col   = c_ends[_c_mask]
                else:
                    _c_starts_col = c_starts; _c_ends_col = c_ends
                if len(o_starts):
                    _o_mask       = (cols[o_starts] >= _cc_lo) & (cols[o_starts] <= _cc_hi)
                    _o_starts_col = o_starts[_o_mask]
                    _o_ends_col   = o_ends[_o_mask]
                else:
                    _o_starts_col = o_starts; _o_ends_col = o_ends
            else:
                blk_rows = _tgt_rows_src[blk_lo:blk_hi]
                blk_cols = _tgt_cols_src[blk_lo:blk_hi]
                blk_pts  = _tgt_vals_src[blk_lo:blk_hi]
                blk_scnr = _tgt_scnr_src[blk_lo:blk_hi]
                blk_n    = blk_hi - blk_lo
                _c_starts_col = c_starts; _c_ends_col = c_ends
                _o_starts_col = o_starts; _o_ends_col = o_ends

            _blk_row_lo = need_row_lo  # kept for blk_pts row-range reference

            chunk_num += 1

            # Debug/experiment per-chunk timing: snapshot before this chunk's
            # work starts, compared against the same snapshot taken at the
            # end of the loop body below. Off by default (cfg._DEBUG_PER_CHUNK_CAPTURE).
            _dbg_per_chunk = getattr(_cfg, '_DEBUG_PER_CHUNK_CAPTURE', False)
            if _dbg_per_chunk:
                _dbg_t0 = _pt()
                _dbg_wall0 = _wall_pt()
                _dbg_contain0 = _t_contain
                _dbg_overlap0 = _t_overlap

            # ---- ppc estimate from sparse coarse density map --------------------
            # Query the coarse cell region covering this chunk (strip rows × col range).
            # Handles tall strips and col splits correctly — both dimensions matter.
            if _cfg.USE_WEIGHTED_ND_DECISION:
                if _cc_lo is not None:
                    _reg_nz, _reg_cnt = _sparse_box_ppc_and_counts(_cr_lo, _cr_hi, _cc_lo // _K, _cc_hi // _K)
                    _col_span_coarse = (_cc_hi // _K) - (_cc_lo // _K) + 1
                else:
                    _reg_nz, _reg_cnt = _sparse_box_ppc_and_counts(_cr_lo, _cr_hi)
                    _col_span_coarse = _max_cc_coarse
                _row_span_coarse = _cr_hi - _cr_lo + 1
                _total_area_fine = max(1, _row_span_coarse * _col_span_coarse * _K * _K)
                _total_pts_reg = float(_reg_cnt.sum()) if len(_reg_cnt) else 0.0
                _total_area_ppc = (_total_pts_reg / _total_area_fine) * math.pi * _sr * _sr
                _ppc_est = float(np.percentile(_reg_nz, _PPC_CHUNK_PERCENTILE)) if len(_reg_nz) > 0 else 1.0
                # Share of coarse blocks in the chunk's bounding box that are
                # actually occupied -- how much total_area_ppc's full-footprint
                # averaging dilutes true local density. High share: little
                # empty space, the average is close to unbiased. Low share:
                # points cluster in a few blocks out of many, and the average
                # badly understates density where points actually are.
                _n_blocks_possible = max(1, _row_span_coarse * _col_span_coarse)
                _share_occupied = len(_reg_nz) / _n_blocks_possible
            else:
                if _cc_lo is not None:
                    _reg_nz = _sparse_box_ppc(_cr_lo, _cr_hi, _cc_lo // _K, _cc_hi // _K)
                else:
                    _reg_nz = _sparse_box_ppc(_cr_lo, _cr_hi)
                _ppc_est   = float(np.percentile(_reg_nz, _PPC_CHUNK_PERCENTILE)) if len(_reg_nz) > 0 else 1.0

            # ---- per-chunk adaptive nd selection --------------------------------
            # _chunk_single_region is resolved from the CHOSEN TAG, not the static
            # cfg.SINGLE_REGION flag: adaptive selection can dispatch a
            # single_region=True tag (sT, s1T, smNT — smNT always is one, by
            # construction) on any chunk regardless of that global flag, and the
            # contain/overlap templates must match whichever tag was actually chosen.
            _is_super_cell_nd = False
            if _TEST_RANDOM_ND:
                import random as _rng
                _chunk_nd = _rng.choice(_nd_range_test)
                _chunk_tag = (_chunk_nd, False)
                _chunk_single_region = _chunk_tag[1]
                _nd_chunk_counts[_chunk_tag] = _nd_chunk_counts.get(_chunk_tag, 0) + 1
                _is_super_cell_nd = (_chunk_nd < 0)
                if _is_super_cell_nd:
                    if _do_weight:
                        _ensure_super_cell_nd(_chunk_nd)
                else:
                    shared_cntd_offset     = _nd_shared_cntd[_chunk_nd]
                    cntd_offset_by_region  = _nd_cntd_by_reg[_chunk_nd]
                    ovlpd_offset_by_region = _nd_ovlpd_by_reg[_chunk_nd]
                    if _chunk_single_region:
                        single_region_ovlpd_offset = _nd_sr_ovlpd[_chunk_nd]
            else:
                if _cfg.USE_WEIGHTED_ND_DECISION:
                    _chunk_tag = _best_nd_tag_weighted(_sr, _reg_nz, _reg_cnt, _total_area_ppc,
                                                        share_occupied=_share_occupied)
                else:
                    _chunk_tag = _best_nd_tag(_sr, _ppc_est)
                _chunk_nd  = _chunk_tag[0]
                # nd_choice's cost model is built independent of the actual
                # runtime-selected native nest_depth for this grid/radius — clamp
                # so a recommendation deeper than what geometry was built for
                # (_nd_max) never causes an out-of-bounds dict lookup below.
                #
                # RESOLVED (was TODO(perf)): previously this silently, permanently
                # downgraded any chunk whose cost model wanted nd > _nd_max to
                # _nd_max, for the whole search -- measured 2026-07 via
                # ND_CLAMP_STATS: up to 62.5% of chunks at r=3000 on real
                # 521k-point data. Now calls _ensure_deep_nd, which extends
                # geometry to exactly the needed depth on first use (mirrors
                # _ensure_super_cell_nd's lazy-build pattern for nd<0), reusing
                # build_disk_region_lookups' own disk_region_cache check so a
                # depth already built earlier in the process costs nothing.
                # Only the FIRST chunk in a search that needs a given deeper nd
                # pays the build cost; every subsequent chunk at that nd or
                # shallower reuses it for free, same as the upfront 0..nest_depth
                # entries built at the top of this function.
                _dbg_nd_clamp_total += 1
                if _chunk_nd > _nd_max:
                    _dbg_nd_clamp_hits += 1
                    # Amortization rule: build depth d if predicted_time_saved(d)
                    # > predicted_build_cost(d) * GEO_AMORTIZATION_WEIGHT (same
                    # rule and same weight the upfront nest_depth choice uses).
                    # Tally-based so the decision isn't made on one chunk in
                    # isolation: every chunk wanting a deeper nd than what's
                    # built adds its own point mass (blk_n, this chunk's actual
                    # target-point count) to _dnd_pt_tally[nd], keyed by the
                    # EXACT depth it wanted. Re-evaluated on every such event
                    # (cheap: a dict scan over at most a handful of distinct nd
                    # levels), this asks: for each candidate depth d > _nd_max,
                    # how many points TOTAL seen so far would benefit from
                    # being at nd>=d (a chunk that wanted nd=8 also "wants"
                    # nd=7,6,... since a deeper build always helps a shallower
                    # need too) -- then applies the rule above to that whole
                    # accumulated mass and picks the DEEPEST d that still pays
                    # off, so a late-arriving straggler benefits retroactively
                    # from mass already tallied by earlier same-depth chunks.
                    # Track the ACTUAL ppc that made this chunk want _chunk_nd
                    # (_ppc_est, already computed above), not a benchmark-table
                    # artifact -- a tag's own lowest-benchmarked ppc can sit
                    # BELOW the ppc crossover where that tag actually wins,
                    # making it look artificially expensive (confirmed via
                    # DEEP_ND_CANDIDATE tracking this session: using
                    # min(_BENCH[...][tag]) produced savings_ms in the
                    # -6M..-200M range for every candidate, i.e. deeper nd
                    # always looked like a loss -- because the benchmark
                    # table's low breakpoint for a deep tag is often well
                    # below where that tag is genuinely optimal). Weighted
                    # mean of real observed ppc, accumulated per bucket.
                    _dnd_pt_tally[_chunk_nd] = _dnd_pt_tally.get(_chunk_nd, 0) + blk_n
                    _dnd_ppc_sum[_chunk_nd] = _dnd_ppc_sum.get(_chunk_nd, 0.0) + _ppc_est * blk_n
                    _remaining_pts = (int(_remaining_src_pts_from[_strip_i])
                                       if _strip_i < len(_remaining_src_pts_from) else 0)
                    if _remaining_pts > 0:
                        # Time-saved side: use the SAME per-chunk cost model
                        # _fit_penalty's cost-weighted merge decision already
                        # relies on (nd_choice._BENCH / _nd_cost_at_array,
                        # via _sr_bench_key) -- real ms-per-point benchmarks
                        # keyed by (sr, tag, ppc), not spacing_topology's
                        # coarse dataset-level regression (which needed a
                        # guessed skewness and was measured ~7x off on a
                        # direct nd=1 build-time check this session). The
                        # representative ppc per bucket is a point-weighted
                        # mean of each chunk's OWN actual _ppc_est
                        # (_dnd_ppc_sum / _dnd_pt_tally) -- NOT a benchmark-
                        # table breakpoint. An earlier version used the
                        # tag's lowest benchmarked ppc as a stand-in, which
                        # produced nonsense (savings_ms in the -6M..-200M
                        # range for every candidate, via DEEP_ND_CANDIDATE
                        # tracking) because a tag's low breakpoint often
                        # sits BELOW the ppc crossover where that tag
                        # actually wins -- using the chunk's real ppc (which
                        # is, by construction, already past that crossover,
                        # since that's why _best_nd_tag picked this tag for
                        # it) fixes that.
                        # Build-cost side stays from predict_timing's geo_s --
                        # nd_choice's table has no geometry-build timings at
                        # all, only search/dispatch cost, so that half of the
                        # estimate is a genuinely different question.
                        from aabpl.search.spacing_topology import predict_timing as _predict_timing
                        _dnd_r_over_s = r / grid_spacing
                        _dnd_best_d, _dnd_best_net = _nd_max, 0.0
                        for _dnd_d in range(_nd_max + 1, max(_dnd_pt_tally) + 1):
                            _dnd_pts_at_d = sum(cnt for lvl, cnt in _dnd_pt_tally.items() if lvl >= _dnd_d)
                            if _dnd_pts_at_d <= 0:
                                continue
                            _dnd_ppc_wsum_at_d = sum(s for lvl, s in _dnd_ppc_sum.items() if lvl >= _dnd_d)
                            _dnd_ppc_repr = _dnd_ppc_wsum_at_d / _dnd_pts_at_d
                            _dnd_tag_deep = (_dnd_d, False)
                            _dnd_tag_shallow = (_nd_max, False)
                            _dnd_cost_per_pt_deep = float(_nd_cost_at_array(
                                _sr_bench_key, _dnd_tag_deep, np.array([_dnd_ppc_repr]))[0])
                            _dnd_cost_per_pt_shallow = float(_nd_cost_at_array(
                                _sr_bench_key, _dnd_tag_shallow, np.array([_dnd_ppc_repr]))[0])
                            _dnd_savings_ms = (_dnd_cost_per_pt_shallow - _dnd_cost_per_pt_deep) * _dnd_pts_at_d
                            _geo_s, _, _ = _predict_timing(
                                _dnd_r_over_s, _dnd_d, _remaining_pts, _dnd_pts_at_d, r,
                                _dnd_spatial_width, _dnd_spatial_height, 10.0,
                                geometry_cached=False)
                            _dnd_cost_ms = _geo_s * 1000.0 * _cfg.GEO_AMORTIZATION_WEIGHT
                            _dnd_net = _dnd_savings_ms - _dnd_cost_ms
                            if _dbg_phase:
                                print(f'DEEP_ND_CANDIDATE d={_dnd_d} from_nd_max={_nd_max} '
                                      f'pts_at_d={_dnd_pts_at_d:.0f} ppc_repr={_dnd_ppc_repr:.2f} '
                                      f'savings_ms={_dnd_savings_ms:.2f} build_geo_s={_geo_s:.3f} '
                                      f'cost_ms(after_amort)={_dnd_cost_ms:.2f} net_ms={_dnd_net:.2f}',
                                      flush=True)
                            if _dnd_net > _dnd_best_net:
                                _dnd_best_d, _dnd_best_net = _dnd_d, _dnd_net
                        if _dnd_best_d > _nd_max:
                            if _dbg_phase:
                                print(f'DEEP_ND_BUILD chosen_d={_dnd_best_d} '
                                      f'net_ms={_dnd_best_net:.2f} (was _nd_max={_nd_max})', flush=True)
                            _ensure_deep_nd(_dnd_best_d)
                    if _chunk_nd > _nd_max:
                        _chunk_nd = _nd_max
                        _chunk_tag = (_chunk_nd, _chunk_tag[1])
                _chunk_single_region = _chunk_tag[1]
                _nd_chunk_counts[_chunk_tag] = _nd_chunk_counts.get(_chunk_tag, 0) + 1
                _is_super_cell_nd = (_chunk_nd < 0)
                if _is_super_cell_nd:
                    if _do_weight:
                        # area-weighting still uses the old per-position (parity)
                        # templates — not yet ported to the coarse-batch fast path.
                        # Only the sparse/supercell chunks pay this; nd>=0 chunks in
                        # the same run are unaffected either way.
                        #
                        # Port plan, if this combination turns out to matter in
                        # practice: split by area_weight mode.
                        #   'logit'/'flat'/'binary' (cheap, not 'exact'): classify
                        #     each COARSE candidate cell once as interior/exterior/
                        #     boundary via Shapely against study_area (few dozen
                        #     coarse cells per nd, cheap — same idea as the existing
                        #     _do_exact_supercell block classification, just at
                        #     coarse-cell size instead of exact_block_k). Then reuse
                        #     the existing centroid-distance logit/flat estimate per
                        #     point, keyed off that classification, inside
                        #     _process_super_cell_chunk. No _expand_coarse needed.
                        #   'exact': needs true per-point disk∩study_area area, which
                        #     is the expensive/precise path already — porting it
                        #     doesn't remove that cost, only the (now-fixed)
                        #     geometry-build overhead around it. Lower priority;
                        #     scope separately if ever needed.
                        # Not started because it's unconfirmed whether area_weight
                        # is actually used together with sparse (supercell-triggering)
                        # data in practice — check before investing in this.
                        _ensure_super_cell_nd(_chunk_nd)
                else:
                    shared_cntd_offset     = _nd_shared_cntd[_chunk_nd]
                    cntd_offset_by_region  = _nd_cntd_by_reg[_chunk_nd]
                    ovlpd_offset_by_region = _nd_ovlpd_by_reg[_chunk_nd]
                    if _chunk_single_region:
                        single_region_ovlpd_offset = _nd_sr_ovlpd[_chunk_nd]

            if _cc_lo is not None:
                _col_slice = cols[batch_pt_start:batch_pt_end]
                _chunk_mask = (_col_slice >= _cc_lo) & (_col_slice <= _cc_hi)
                _n_src_chunk = int(np.sum(_chunk_mask))
            else:
                _chunk_mask = None
                _n_src_chunk = batch_pt_end - batch_pt_start

            # ---- super-cell fast path: coarse-cell batching, skip fine machinery -
            if _is_super_cell_nd and not _do_weight:
                if _cc_lo is not None:
                    _sp_mask = ((cols[batch_pt_start:batch_pt_end] >= _cc_lo) &
                                (cols[batch_pt_start:batch_pt_end] <= _cc_hi))
                    _sp_idx = np.flatnonzero(_sp_mask) + batch_pt_start
                else:
                    _sp_idx = np.arange(batch_pt_start, batch_pt_end)
                _process_super_cell_chunk(_chunk_nd, _sp_idx)
                if len(_sp_idx) and int(_sp_idx.max()) >= next_threshold:
                    next_threshold = progress.update(int(_sp_idx.max()))
                if _dbg_per_chunk:
                    if not hasattr(_cfg, '_DEBUG_PER_CHUNK_STATS'):
                        _cfg._DEBUG_PER_CHUNK_STATS = []
                    _cfg._DEBUG_PER_CHUNK_STATS.append({
                        'chunk_num': chunk_num,
                        'cc_lo': _cc_lo, 'cc_hi': _cc_hi,
                        'n_src': len(_sp_idx),
                        'nd': _chunk_nd,
                        'ppc_est': _ppc_est,
                        'is_super_cell': True,
                    })
                continue

            # ---- pre-aggregate pts -> cell slices within block ------------------
            # block_cell_slices: absolute-codec-key -> (start, end) into blk_pts.
            # Used by the overlap path (gather raw pts into candidate_buffer).
            # Contains level-0 entries (one per occupied cell) plus level-1..nd
            # sub-cell entries built by _nest_block_slices quadtree descent.
            _tbcs0 = _pt()
            block_cell_slices = {}
            if _do_weight and not _do_exact_supercell:
                block_cell_valid_area = {}
            if blk_n > 0:
                cell_bdry = np.concatenate([[True],
                                            (blk_rows[1:] != blk_rows[:-1]) |
                                            (blk_cols[1:] != blk_cols[:-1])])
                cell_starts = _np_flatnonzero(cell_bdry)
                cell_ends   = _np_append(cell_starts[1:], blk_n)
                # Vectorised key computation: codec.key() already accepts array
                # inputs (np.asarray/np.round throughout), but was previously
                # called once per occupied cell -- a full scaled-int computation
                # plus numpy-call overhead per call for what's really one
                # elementwise formula. One batched call instead of N.
                _cell_keys = codec.key(0, blk_rows[cell_starts], blk_cols[cell_starts]).astype(np.int64)
                for _ck, s, e in zip(_cell_keys.tolist(), cell_starts.tolist(), cell_ends.tolist()):
                    block_cell_slices[_ck] = (s, e)
                    if _do_weight and not _do_exact_supercell:
                        if _ck in invalid_cell_keys:
                            va = 0.0
                        elif _ck in boundary_cell_keys:
                            # .get(..., 1.0) not [_ck]: boundary_cell_fracs can be
                            # empty (compute_fractions=False) while boundary_cell_keys
                            # is still populated from a different source (partial_cells_rc)
                            # -- confirmed pre-existing KeyError in that gap, reproduces
                            # identically in pip 0.4.1. Defaulting to 1.0 (fully valid,
                            # no extra invalid-area penalty) is the conservative choice:
                            # it never crashes, and is a no-op whenever the dict IS
                            # populated (the common case, unaffected by this change).
                            va = (boundary_cell_fracs.get(_ck, 1.0) if _do_per_cell else _bnd_frac) * _cell_area
                        else:
                            va = _cell_area
                        block_cell_valid_area[_ck] = va
                _nest_block_slices(blk_scnr, blk_rows, blk_cols, blk_n, block_cell_slices,
                                   depth=max(0, _chunk_nd))
            _t_block_cell_slices_build += _pt() - _tbcs0

            if _cfg.PROFILE_FUNC_TIMES:
                block_cell_counts = {k: e - s for k, (s, e) in block_cell_slices.items()}
            nonempty_block = set(block_cell_slices)

            # ---- overlap gather cache keyed on occupied-cell fingerprint --------
            # fs = frozenset of absolute codec keys that are both in the disk template
            # and in block_cell_slices (i.e. actually occupied by target points).
            # Cache stores (slice_list, n) so hits re-fill candidate_buffer without
            # re-scanning block_cell_slices.
            _FP_CAP = 512
            overlap_fp_cache = {}

            def gather_overlap_pts(codec_offsets, home_codec_key):
                nonlocal _n_o_cache_hit, _n_o_cache_miss, _n_ov_keys, _n_ov_template
                nonlocal _t_ov_filter, _t_ov_cachelkup, _t_ov_copy
                nonlocal candidate_buffer, _n_buffer_resizes
                _tf0 = _pt()
                raw_keys = codec_offsets + home_codec_key
                _n_ov_template += len(codec_offsets)
                # Check cache with a cheap frozenset key first; only sort on miss.
                occupied_overlap_keys = frozenset(int(k) for k in raw_keys if k in nonempty_block)
                _n_ov_keys += len(occupied_overlap_keys)
                _t_ov_filter += _pt() - _tf0
                _tc0 = _pt()
                cached = overlap_fp_cache.get(occupied_overlap_keys)
                _t_ov_cachelkup += _pt() - _tc0
                _tcp0 = _pt()
                if cached is not None:
                    _n_o_cache_hit += 1
                    s_arr, lens, total_pts = cached
                else:
                    _n_o_cache_miss += 1
                    if occupied_overlap_keys:
                        # dict lookup itself still needs a per-key Python loop (keys
                        # come from a dict, not a sorted array), but the actual point
                        # COPY below is now one batched fancy-index gather instead of
                        # one small numpy slice-assign per key -- that copy loop was
                        # measured as ~60-67% of total gather time (see bench_overlap_micro.py).
                        s_list = []
                        e_list = []
                        for k in occupied_overlap_keys:
                            s, e = block_cell_slices[k]
                            s_list.append(s); e_list.append(e)
                        s_arr = _np_array(s_list, dtype=np.int64)
                        lens  = _np_array(e_list, dtype=np.int64) - s_arr
                        total_pts = int(lens.sum())
                    else:
                        s_arr = _np_array([], dtype=np.int64)
                        lens  = _np_array([], dtype=np.int64)
                        total_pts = 0
                    if len(overlap_fp_cache) < _FP_CAP:
                        # cache compact (start, length) arrays, not the materialised
                        # candidate points -- rebuilding the combined index below is
                        # cheap (vectorised) relative to the copy it replaces, so this
                        # keeps the cache small while still letting hits skip the
                        # per-key dict-lookup loop entirely.
                        overlap_fp_cache[occupied_overlap_keys] = (s_arr, lens, total_pts)
                if total_pts:
                    # Vectorised "concatenate multiple ranges" gather: build one
                    # combined index array covering all (s, s+len) ranges at once,
                    # then a single fancy-index copy -- replaces what used to be
                    # one candidate_buffer[...] = blk_pts[s:e] numpy call per key.
                    seg_offsets  = np.cumsum(lens) - lens
                    combined_idx = (np.repeat(s_arr, lens)
                                    + np.arange(total_pts) - np.repeat(seg_offsets, lens))
                    if total_pts > len(candidate_buffer):
                        # Upfront max_cands estimate (cell_count_iter, based on
                        # id_to_sums) undercounts whenever part of the grid is
                        # supercell-only (nd<0 regions skip id_to_sums entirely
                        # -- see aggregate_point_data_to_cells_adaptive_nd's
                        # docstring) while a nearby nd>=0 chunk's overlap
                        # template still reaches into a genuinely dense region.
                        # Rather than trying to compute a tighter upfront bound
                        # (same class of bug could resurface for a different
                        # density shape), grow on demand -- rare in practice,
                        # correctness matters more than avoiding a realloc.
                        _n_buffer_resizes += 1
                        candidate_buffer = _np_zeros((total_pts, n_c + 2), dtype=float)
                    candidate_buffer[:total_pts] = blk_pts[combined_idx]
                _t_ov_copy += _pt() - _tcp0
                return candidate_buffer[:total_pts]

            # ---- merged contain + overlap pass ----------------------------------
            cur_cell_sum   = zero_sum.copy()
            cur_home_key   = None   # absolute codec key of the last-seen home cell
            contain_idx = 0; overlap_idx = 0
            n_contain_groups = len(_c_starts_col); n_overlap_groups = len(_o_starts_col)

            while contain_idx < n_contain_groups or overlap_idx < n_overlap_groups:
                do_contain = (contain_idx < n_contain_groups and
                              (overlap_idx >= n_overlap_groups or
                               _c_starts_col[contain_idx] <= _o_starts_col[overlap_idx]))
                do_overlap = (overlap_idx < n_overlap_groups and
                              (contain_idx >= n_contain_groups or
                               _o_starts_col[overlap_idx] <= _c_starts_col[contain_idx]))

                if do_contain:
                    _n_contain_grps += 1
                    _t0c = _pt()
                    start, end = int(_c_starts_col[contain_idx]), int(_c_ends_col[contain_idx])
                    contain_home_key = int(codec.key(0, int(rows[start]), int(cols[start])))
                    if cur_home_key is None or cell_changed[start] or cur_home_key != contain_home_key:
                        cur_home_key = contain_home_key
                        if _is_super_cell_nd:
                            _step = 2 ** (-_chunk_nd)
                            _par  = (int(rows[start]) % _step) * _step + (int(cols[start]) % _step)
                            cur_cell_sum = _contain_sum(_nd_super_shared_cntd[_chunk_nd][_par], cur_home_key)
                        else:
                            cur_cell_sum = _contain_sum(shared_cntd_offset, cur_home_key)
                    if _chunk_single_region:
                        sums_within_disks[start:end] += cur_cell_sum
                    else:
                        region_trgl_id = region_and_trgl[start]
                        region_sum = _contain_sum(cntd_offset_by_region[region_trgl_id], cur_home_key)
                        sums_within_disks[start:end] += cur_cell_sum + region_sum
                    if _cfg.PROFILE_FUNC_TIMES:
                        _cntd_keys_for_prof = (list(shared_cntd_offset + cur_home_key) if _chunk_single_region else
                            list(shared_cntd_offset + cur_home_key) + list(cntd_offset_by_region[region_trgl_id] + cur_home_key))
                        _n_contain_pts += (end - start) * sum(
                            block_cell_counts.get(k, 0)
                            for k in set(_cntd_keys_for_prof) if k in nonempty_block
                        )
                    if _do_weight and not _do_exact_supercell:
                        cr_id = cell_region[start]
                        cache_key = (cur_home_key, cr_id)
                        cached_invalid_area = va_contain_cache.get(cache_key)
                        if cached_invalid_area is None:
                            contain_cell_keys = set(int(k) for k in (contain_l0_offsets[cr_id] + cur_home_key))
                            n_invalid_cells = len(contain_cell_keys & invalid_cell_keys)
                            if _do_binary:
                                # invalid_cell_keys contains all non-fully-inside cells; apply
                                # _non_fully_frac as average valid fraction across that set
                                invalid_area_contrib = (1.0 - _non_fully_frac) * n_invalid_cells * _cell_area
                            else:
                                invalid_area_contrib = n_invalid_cells * _cell_area
                                if boundary_cell_keys:
                                    boundary_keys = contain_cell_keys & boundary_cell_keys
                                    if _do_per_cell:
                                        for k in boundary_keys:
                                            # see .get(..., 1.0) rationale at the
                                            # other boundary_cell_fracs site above
                                            invalid_area_contrib += (1.0 - boundary_cell_fracs.get(k, 1.0)) * _cell_area
                                    else:  # flat
                                        invalid_area_contrib += (1.0 - _bnd_frac) * len(boundary_keys) * _cell_area
                            va_contain_cache[cache_key] = invalid_area_contrib
                            cached_invalid_area = invalid_area_contrib
                        invalid_area[start:end] += cached_invalid_area
                    if end - 1 >= next_threshold:
                        next_threshold = progress.update(end - 1)
                    contain_idx += 1
                    _t_contain += _pt() - _t0c

                if do_overlap:
                    _n_overlap_grps += 1
                    _t0o = _pt()
                    start, end = int(_o_starts_col[overlap_idx]), int(_o_ends_col[overlap_idx])
                    overlap_home_key = int(codec.key(0, int(rows[start]), int(cols[start])))
                    _t0g = _pt()
                    if _is_super_cell_nd:
                        _step = 2 ** (-_chunk_nd)
                        _par  = (int(rows[start]) % _step) * _step + (int(cols[start]) % _step)
                        _ovlpd_tmpl = (_nd_super_sr_ovlpd[_chunk_nd][_par] if _chunk_single_region
                                       else ovlpd_offset_by_region.get(region_and_trgl[start],
                                                                        _nd_super_shared_cntd[_chunk_nd][_par]))
                    else:
                        _ovlpd_tmpl = (single_region_ovlpd_offset if _chunk_single_region
                                       else ovlpd_offset_by_region[region_and_trgl[start]])
                    candidates = gather_overlap_pts(_ovlpd_tmpl, overlap_home_key)
                    _t_ov_gather += _pt() - _t0g
                    _n_candidates += len(candidates)
                    if _do_weight and not _do_exact_supercell:
                        cr_id = cell_region[start]
                        overlap_cell_keys = set(int(k) for k in (overlap_l0_offsets[cr_id] + overlap_home_key))
                        invalid_overlap_keys = overlap_cell_keys & invalid_cell_keys
                        boundary_overlap_keys = overlap_cell_keys & boundary_cell_keys
                        if invalid_overlap_keys or boundary_overlap_keys:
                            # logit / flat / binary: vectorised centroid path.
                            # Build combined cell list with per-cell invalid weights:
                            #   inv cells  → full invalid weight (1.0 or 1-_non_fully_frac)
                            #   bnd cells  → partial invalid weight (per-cell or fixed frac)
                            invalid_cell_rcs  = []
                            invalid_cell_weights = []
                            invalid_weight = (1.0 - _non_fully_frac) if _do_binary else 1.0
                            for k in invalid_overlap_keys:
                                invalid_cell_rcs.append(codec.decode_tuple(k)[1])
                                invalid_cell_weights.append(invalid_weight)
                            if _do_per_cell:
                                for k in boundary_overlap_keys:
                                    invalid_cell_rcs.append(codec.decode_tuple(k)[1])
                                    # see .get(..., 1.0) rationale at the other
                                    # boundary_cell_fracs sites above
                                    invalid_cell_weights.append(1.0 - boundary_cell_fracs.get(k, 1.0))
                            elif _do_binary:
                                for k in boundary_overlap_keys:
                                    invalid_cell_rcs.append(codec.decode_tuple(k)[1])
                                    invalid_cell_weights.append(1.0 - _non_fully_frac)
                            else:  # flat
                                for k in boundary_overlap_keys:
                                    invalid_cell_rcs.append(codec.decode_tuple(k)[1])
                                    invalid_cell_weights.append(1.0 - _bnd_frac)
                            if invalid_cell_rcs:
                                src_xy_block = point_xy[start:end]
                                centroids = _np_array(
                                    [get_cell_centroid(rr, cc) for rr, cc in invalid_cell_rcs])
                                weights = _np_array(invalid_cell_weights)
                                centroid_dists = np_norm(
                                    src_xy_block[:, None, :] - centroids[None, :, :], axis=2)
                                # Prefilter: centroid farther than r + half_diag means
                                # no corner of the cell can touch the disk → zero share.
                                if _do_logit:
                                    share = 1 - 1 / (1 + _logit_Q * _np_exp(
                                        -_logit_B * (centroid_dists / r - 1)))
                                    share[centroid_dists > r + _half_diag] = 0.0
                                else:
                                    # flat / binary: uniform weight per cell, just filter
                                    share = (centroid_dists <= r + _half_diag).astype(float)
                                invalid_area[start:end] += (
                                    share * weights[None, :]).sum(axis=1) * _cell_area
                    if len(candidates):
                        cand_xy   = candidates[:, -2:]
                        cand_vals = candidates[:, :n_c].astype(float)
                        _t0d = _pt()
                        # OVERLAP_BLOCK caps rows per batch but not candidate count --
                        # when cand_xy itself is huge (e.g. extreme ppc where most of
                        # the dataset is within range), even one OVERLAP_BLOCK-sized
                        # row batch can blow past several GB. _batched_disk_sum
                        # enforces the MAX_DIST_MATRIX_BYTES cap regardless of
                        # OVERLAP_BLOCK, further sub-batching by rows when needed.
                        for blk_s in range(start, end, OVERLAP_BLOCK):
                            blk_e        = min(blk_s + OVERLAP_BLOCK, end)
                            src_xy_block = point_xy[blk_s:blk_e]
                            sums_within_disks[blk_s:blk_e] += _batched_disk_sum(
                                src_xy_block, cand_xy, cand_vals, r2)
                        _t_ov_distcheck += _pt() - _t0d
                    if end - 1 >= next_threshold:
                        next_threshold = progress.update(end - 1)
                    overlap_idx += 1
                    _t_overlap += _pt() - _t0o

            # Debug/experiment per-chunk timing: record this chunk's elapsed
            # time and its own contribution to the contain/overlap
            # ("cell aggregation") sub-timers. Not reached by the supercell
            # fast path above (which `continue`s past this point) -- only
            # meaningful for nd>=0 fixed-nd experiments.
            if _dbg_per_chunk:
                if not hasattr(_cfg, '_DEBUG_PER_CHUNK_STATS'):
                    _cfg._DEBUG_PER_CHUNK_STATS = []
                _dbg_row = {
                    'chunk_num': chunk_num,
                    'cc_lo': _cc_lo, 'cc_hi': _cc_hi,
                    'n_src': _n_src_chunk,
                    'n_tgt': blk_n,
                    'nd': _chunk_nd,
                    'ppc_est': _ppc_est,
                }
                # Occupancy/count-weighted covariates (only in scope when
                # USE_WEIGHTED_ND_DECISION computed them above) -- n_occupied
                # (distinct occupied coarse blocks), total point count across
                # them, and the occupancy-aware total-chunk-area ppc. Worth
                # tracking as regression covariates for the chunk-overhead
                # cost model regardless of whether they drove this chunk's
                # actual nd choice (fixed-nd experiments force nd separately).
                if _cfg.USE_WEIGHTED_ND_DECISION:
                    _dbg_row['n_occupied'] = int(len(_reg_nz))
                    _dbg_row['total_pts_reg'] = _total_pts_reg
                    _dbg_row['total_area_ppc'] = _total_area_ppc
                if getattr(_cfg, '_DEBUG_TRACK_L3_MEMORY', False):
                    # Real object sizes vs. what the row-strip planner's L3
                    # budget formula (pt_bytes/cell_bytes, see l3_budget above)
                    # actually assumes -- the formula only ever accounts for
                    # blk_pts (target-value-xy block, matches exactly by
                    # construction) and a flat 8 bytes/occupied-cell for
                    # block_cell_slices. It never counts: (a) the REAL Python
                    # dict overhead of block_cell_slices (int key + tuple
                    # value, far more than 8 bytes/entry), or (b) any
                    # source-point memory at all (point_xy slices touched
                    # during contain/overlap). This directly measures both
                    # gaps so we can compare real vs assumed L3 pressure.
                    _real_tgt_bytes = int(blk_pts.nbytes)
                    _real_cell_dict_bytes = int(
                        _sys_getsizeof(block_cell_slices)
                        + sum(_sys_getsizeof(k) for k in block_cell_slices)
                        + sum(_sys_getsizeof(v) for v in block_cell_slices.values())
                    )
                    _assumed_cell_bytes = len(block_cell_slices) * cell_bytes
                    _real_src_xy_bytes = int(_n_src_chunk * point_xy.itemsize * point_xy.shape[1])
                    _assumed_bytes_formula = blk_n * pt_bytes + len(block_cell_slices) * cell_bytes
                    _real_bytes_total = _real_tgt_bytes + _real_cell_dict_bytes + _real_src_xy_bytes
                    _dbg_row['real_tgt_bytes'] = _real_tgt_bytes
                    _dbg_row['real_cell_dict_bytes'] = _real_cell_dict_bytes
                    _dbg_row['assumed_cell_bytes'] = _assumed_cell_bytes
                    _dbg_row['real_src_xy_bytes'] = _real_src_xy_bytes
                    _dbg_row['assumed_bytes_formula'] = _assumed_bytes_formula
                    _dbg_row['real_bytes_total'] = _real_bytes_total
                    _dbg_row['l3_budget_bytes'] = l3_budget
                _cfg._DEBUG_PER_CHUNK_STATS.append(_dbg_row)
                _dbg_row_ref = _cfg._DEBUG_PER_CHUNK_STATS[-1]
                _dbg_row_ref.update({
                    'process_time_ms': (_pt() - _dbg_t0) * 1000,
                    'wall_time_ms': (_wall_pt() - _dbg_wall0) * 1000,
                    'aggregation_time_ms': ((_t_contain - _dbg_contain0) + (_t_overlap - _dbg_overlap0)) * 1000,
                })

        # end col chunk loop

        # Stream this chunk's sums into _stream_out (avoids pandas read-only COW issue).
        for _ci, _cn in enumerate(sum_radius_names):
            _stream_out[_cn][batch_pt_start:batch_pt_end] += \
                sums_within_disks[batch_pt_start:batch_pt_end, _ci]
        sums_within_disks[batch_pt_start:batch_pt_end] = 0.0

        row_iter += 1  # row_chunk_idx/chunk_rows now come from the _strip_plans
                       # tuple each iteration -- no manual advance needed.

    progress.done()

    # ---- write streamed results back to pts_source ------------------------------
    # _stream_out was accumulated in the (possibly chunk-sorted) working-array
    # position space; pts_source itself was never reordered, so map back via
    # the inverse permutation when a chunk sort was applied.
    for _cn in sum_radius_names:
        if _inv_perm_for_writeback is not None:
            pts_source[_cn] = _stream_out[_cn][_inv_perm_for_writeback]
        else:
            pts_source[_cn] = _stream_out[_cn]

    if exclude_self:
        if grid._search_class.tgt_df_contains_src_df:
            for sum_name, value_col in zip(sum_radius_names, c):
                pts_source[sum_name] = pts_source[sum_name].values - pts_source[value_col]
        else:
            progress_print(
                "Option `exclude_self=True` but source and target DataFrames differ — "
                "point own values are not subtracted."
            )

    if _do_weight:
        valid_area_shares = (full_disk_area - invalid_area) / full_disk_area
        share_name = f'valid_area_share_{r}'
        pts_source[share_name] = valid_area_shares
        _safe_shares = np.where(valid_area_shares > 0, valid_area_shares, np.nan)
        for sum_name in sum_radius_names:
            if _keep_raw:
                pts_source[sum_name + '_raw'] = pts_source[sum_name].values
            pts_source[sum_name] = pts_source[sum_name].values / _safe_shares

        # Area-path diagnostics: attached to grid (not just printed) so the
        # weighting pass can be inspected/asserted-on directly, same pattern
        # as area_weight_validation/sum_validation below. n_buffer_resizes
        # tracks how often candidate_buffer's upfront size estimate (based on
        # cell_count_iter/id_to_sums, which undercounts whenever part of the
        # grid is supercell-only) had to grow on demand -- see the dynamic
        # resize fix in gather_overlap_pts. Non-zero here on a mode/dataset
        # that previously crashed is the direct signal the fix is engaging.
        _boundary_mask = valid_area_shares < 0.999
        if not hasattr(grid, 'area_weight_diagnostics'):
            grid.area_weight_diagnostics = {}
        grid.area_weight_diagnostics[area_weight] = {
            'n_points':            int(len(valid_area_shares)),
            'n_boundary_points':   int(np.sum(_boundary_mask)),
            'valid_area_share_min': round(float(np.nanmin(valid_area_shares)), 4) if len(valid_area_shares) else None,
            'valid_area_share_mean': round(float(np.nanmean(valid_area_shares)), 4) if len(valid_area_shares) else None,
            'n_buffer_resizes':    int(_n_buffer_resizes),
        }

        if _cfg.VALIDATE_AREA and getattr(grid, 'study_area', None) is not None:
            from aabpl.testing.validate_area_weight import validate_area_shares as _vaw
            _val = _vaw(pts_source, share_name, grid.study_area, r, x, y)
            _diffs = [abs(c - e) for c, e in _val]
            if not hasattr(grid, 'area_weight_validation'):
                grid.area_weight_validation = {}
            grid.area_weight_validation[area_weight] = {
                'mad':      round(float(sum(_diffs) / len(_diffs)), 6),
                'max_diff': round(float(max(_diffs)), 6),
            }

    # Restore original row order and drop the helper column.
    pts_source.sort_values('_orig_order', inplace=True)
    pts_source.drop(columns=['_orig_order'], inplace=True)

    # Inject sub-timers and counters as synthetic func_timer_dict entries.
    if _cfg.PROFILE_FUNC_TIMES:
        from aabpl.testing.test_performance import func_timer_dict as _ftd
        _now = _pt()
        def _inj(name, val):
            _ftd['times'].append({'func_name': name, 'start_time': _now - val, 'end_time': _now})
        _inj('_saa_contain',      _t_contain)
        _inj('_saa_overlap',      _t_overlap)
        _inj('_saa_ov_gather',    _t_ov_gather)
        _inj('_saa_ov_distcheck', _t_ov_distcheck)
        _inj('_saa_ov_filter',    _t_ov_filter)
        _inj('_saa_ov_cachelkup', _t_ov_cachelkup)
        _inj('_saa_ov_copy',      _t_ov_copy)
        _inj('_saa_super_index_build', _t_super_index_build)
        _inj('_saa_super_loop',        _t_super_loop)
        _inj('_saa_super_gather',      _t_super_gather)
        _inj('_saa_super_distcheck',   _t_super_distcheck)
        _inj('_saa_block_cell_slices_build', _t_block_cell_slices_build)
        # Counters stored as fake 0-duration entries with value in start_time field.
        for name, val in [
            ('_cnt_contain_grps', _n_contain_grps),
            ('_cnt_contain_pts',  _n_contain_pts),
            ('_cnt_overlap_grps', _n_overlap_grps),
            ('_cnt_candidates',   _n_candidates),
            ('_cnt_ov_keys',      _n_ov_keys),
            ('_cnt_super_grps',       _n_super_grps),
            ('_cnt_super_candidates', _n_super_candidates),
            ('_cnt_ov_template',  _n_ov_template),
            ('_cnt_o_cache_hit',  _n_o_cache_hit),
            ('_cnt_o_cache_miss', _n_o_cache_miss),
        ]:
            _ftd['times'].append({'func_name': name, 'start_time': 0, 'end_time': 0, '_count': val})

    if not validate and not _cfg.VALIDATE:
        if _dbg_phase:
            print(f'DISJOINT_PHASE execution+finalize (all strips): {(time.process_time()-_phase_t0)*1000:.2f}ms', flush=True)
            _clamp_rate = (_dbg_nd_clamp_hits / _dbg_nd_clamp_total) if _dbg_nd_clamp_total else 0.0
            print(f'ND_CLAMP_STATS hits={_dbg_nd_clamp_hits} total={_dbg_nd_clamp_total} rate={_clamp_rate:.4f}', flush=True)
        return pts_source[sum_radius_names]

    # ---- brute-force validation -------------------------------------------------
    all_xy   = pts_target[[x, y]].values
    all_vals = pts_target[c].values if n_c > 1 else pts_target[c[0]].values.reshape(-1, 1)
    # Build per-region sorted point lists (descending cell population so denser
    # cells are tested first), then round-robin fill the budget.
    # Budget = min(max(100, n_regions), n_pts_src): cover every region at least
    # once, test at least 100 pts when possible, never exceed source size.
    cell_pop = {(row, col): cnt for row, col, cnt in cell_count_iter(grid) if cnt > 0}
    pts_source['_cell_pop'] = pts_source.apply(
        lambda row: cell_pop.get((int(row[row_name]), int(row[col_name])), 0), axis=1)
    region_pt_lists = (pts_source.sort_values('_cell_pop', ascending=False)
                       .groupby(cell_region_name, sort=False)
                       .apply(lambda g: list(g.index)))
    pts_source.drop(columns=['_cell_pop'], inplace=True)
    n_regions = len(region_pt_lists)
    _val_budget = min(max(100, n_regions), len(pts_source))
    val_indices = []  # list of (cell_region, pt_idx)
    slot = 0
    while len(val_indices) < _val_budget:
        added = False
        for cr, idxs in region_pt_lists.items():
            if slot < len(idxs) and len(val_indices) < _val_budget:
                val_indices.append((cr, idxs[slot]))
                added = True
        if not added:
            break
        slot += 1
    errors = []
    for cr, rep_idx in val_indices:
        rep_xy     = pts_source.loc[rep_idx, [x, y]].values.astype(float)
        dists      = np_norm(all_xy - rep_xy, axis=1)
        brute_sums = all_vals[dists <= r].sum(axis=0).flatten()
        own_vals   = 0.0
        if exclude_self and grid._search_class.tgt_df_contains_src_df:
            own_vals   = all_vals[pts_target.index == rep_idx].sum(axis=0).flatten()
            brute_sums = brute_sums - own_vals
        algo_sums = pts_source.loc[rep_idx, sum_radius_names].values.astype(float).flatten()
        diff = abs(brute_sums - algo_sums).max()
        if diff > 1e-6:
            errors.append((rep_idx, cr, brute_sums, algo_sums, diff))
    if errors:
        progress_print(f"VALIDATION FAILED: {len(errors)}/{len(val_indices)} point(s) have wrong sums (tested {len(val_indices)} pts across {n_regions} region(s)):")
        for rep_idx, cr, bf, algo, diff in errors:
            progress_print(f"  pt_id={rep_idx} cell_region={cr} brute={bf} algo={algo} diff={diff}")
    else:
        progress_print(f"VALIDATION OK: all {len(val_indices)} point(s) correct ({n_regions} region(s), budget={_val_budget}).")

    # Attach results as a grid attribute (mirrors grid.area_weight_validation)
    # so callers/tests can assert on outcomes directly instead of parsing
    # progress_print output.
    if not hasattr(grid, 'sum_validation'):
        grid.sum_validation = {}
    grid.sum_validation[tuple(sum_radius_names)] = {
        'n_tested':  len(val_indices),
        'n_regions': n_regions,
        'n_errors':  len(errors),
        'max_diff':  round(float(max((e[4] for e in errors), default=0.0)), 6),
    }

    if _dbg_phase:
        print(f'DISJOINT_PHASE execution+finalize+validation (all strips): {(time.process_time()-_phase_t0)*1000:.2f}ms', flush=True)
    return pts_source[sum_radius_names]
