/** Wrap `value` into the half-open range [0, span). */
function normalizePhase(value, span) {
    return ((value % span) + span) % span;
}
/**
 * The field-local (column, row) that a die occupies within its reticle field,
 * per the `anchorDie` convention documented on `generateReticleGrid`:
 * `anchorDie` is the field's min-x/min-y (bottom-left) corner, so a die's
 * local position is `die - anchorDie`, wrapped to the field dimensions.
 *
 * This is the single source of truth for "which reticle cell is this die
 * in" — every consumer that needs the answer (e.g. stats findings, region
 * labeling) must call this rather than re-deriving the phase math, so field
 * geometry and cell labeling can never drift apart again.
 */
export function getReticleCell(die, config) {
    const { width, height, anchorDie = { x: 0, y: 0 } } = config;
    const safeWidth = Math.max(1, Math.floor(width));
    const safeHeight = Math.max(1, Math.floor(height));
    return {
        column: normalizePhase(die.x - anchorDie.x, safeWidth),
        row: normalizePhase(die.y - anchorDie.y, safeHeight),
    };
}
/**
 * Generate the grid of reticle rectangles that cover the wafer area.
 * Returns positions in wafer-local coordinates (before orientation rotation).
 * Reticles that don't overlap the wafer circle are excluded.
 *
 * Width and height are in die counts; diePitchX/diePitchY convert to display units.
 * The anchorDie index lands at the reticle's min-x/min-y corner (bottom-left,
 * since +Y is up) — i.e. anchorDie is the first (leftmost, bottom-most) die
 * of the field it belongs to.
 */
export function generateReticleGrid(wafer, config) {
    const { width: W, height: H, diePitchX, diePitchY, anchorDie = { x: 0, y: 0 }, gridOrigin = { x: 0, y: 0 }, } = config;
    const fw = W * diePitchX;
    const fh = H * diePitchY;
    // Phase: which column/row within a field the anchor die occupies.
    const phaseX = normalizePhase(anchorDie.x, W);
    const phaseY = normalizePhase(anchorDie.y, H);
    // Field centres are placed relative to gridOrigin (the physical position of
    // die index (0,0)), not the wafer centre — the die grid may itself be offset
    // from the wafer centre by a non-whole-die-pitch amount.
    const baseX = gridOrigin.x + (phaseX + (W - 1) / 2) * diePitchX;
    const baseY = gridOrigin.y + (phaseY + (H - 1) / 2) * diePitchY;
    // Range of integer k values whose reticle could touch the wafer circle.
    // cx - wafer.center.x = k*fw + (baseX - wafer.center.x), so solve for k against ±range.
    const range = wafer.radius + Math.max(fw, fh);
    const kMinX = Math.ceil((-range - (baseX - wafer.center.x)) / fw);
    const kMaxX = Math.floor((range - (baseX - wafer.center.x)) / fw);
    const kMinY = Math.ceil((-range - (baseY - wafer.center.y)) / fh);
    const kMaxY = Math.floor((range - (baseY - wafer.center.y)) / fh);
    const reticles = [];
    for (let l = kMinY; l <= kMaxY; l++) {
        const cy = baseY + l * fh;
        for (let k = kMinX; k <= kMaxX; k++) {
            const cx = baseX + k * fw;
            // Closest point on this rectangle to the wafer centre
            const closestX = Math.max(cx - fw / 2, Math.min(wafer.center.x, cx + fw / 2));
            const closestY = Math.max(cy - fh / 2, Math.min(wafer.center.y, cy + fh / 2));
            const dx = closestX - wafer.center.x;
            const dy = closestY - wafer.center.y;
            if (dx * dx + dy * dy <= wafer.radius * wafer.radius) {
                reticles.push({ x: cx, y: cy, width: fw, height: fh });
            }
        }
    }
    return reticles;
}
//# sourceMappingURL=reticle.js.map