#!/usr/bin/env python3
"""
===============================================================================
OPTIMIZED ITERATIVE SEGMENTED SIEVE CUBIC FROBENIUS + $620 CHALLENGE HUNTER
CONTINUOUS POLY-2 LEARNING + GAP-SPIRAL ACCELERATED SKIPPING
RAM-FIRST / LOW-I-O LEARNING EDITION -- v7
===============================================================================

IMPORTANT
---------

The exact mathematical sieve remains authoritative.

The learned accelerator is heuristic and may skip a candidate that would
otherwise survive. Therefore:

    --shortcut-search
        = exhaustive mathematical search

    --learned-shortcut-search
        = heuristic accelerated search

The $620 verifier remains authoritative whenever a candidate reaches it.

WHAT CHANGED IN v7
-------------------

v6 carried Poly-2 as an optional second cubic after the tribonacci cubic
Frobenius. Analysis showed that:

    1. The cubic Frobenius (tribonacci polynomial x³ - x² - x - 1) is the
       single strongest filter in the pipeline. Pseudoprimes to it are
       extraordinarily rare -- Grantham's bound puts expected counts below
       any fixed limit as astronomically small.

    2. Poly-2 (x³ - x - 1) is a second cubic operating in a correlated
       algebraic structure. The structural reasons a pseudoprime passes one
       cubic tend to help it pass another, so Poly-2 adds little independent
       discriminating power after the tribonacci cubic has already passed.
       The v6 Poly-2 learner confirmed this: it suspended itself rapidly
       in practice, which is exactly the signal of low marginal value.

    3. The three quadratic vantages (discriminants 5, 8, 13) are each
       genuinely independent of one another and of the cubic. Together with
       the cubic they give coverage across four distinct algebraic structures.
       That is the right combination.

v7 therefore:

    - Removes Poly-2 entirely (the class, the logger fields, the dashboard
      rows, and the pipeline stage).

    - Retains in full:
        * q modular sieve + q primality (cheap coarse filter)
        * mod-5 residue on candidate (free, mandatory for $620)
        * factor-reduced Fermat mod p and mod q (cheap, uses factor structure)
        * Quadratic Fibonacci/Lucas closure (discriminant 5, axes A/B/C)
        * Binary quadratic vantage (discriminant 8, P=-2, Q=-1)
        * Trinary quadratic vantage (discriminant 13, P=-3, Q=-1)
        * Cubic Frobenius (tribonacci: x³ - x² - x - 1)
        * $620 verifier (authoritative, always run after cubic pass)

    - Retains the v6 gap-spiral learned accelerator unchanged. It operates
      on the Tier-0 cubic Frobenius stream exactly as before.

    - Removes the dead-code gate wrappers (quadratic_closure_gate,
      binary_quadratic_vantage_gate, trinary_quadratic_vantage_gate) that
      were defined but not called in the search pipeline.

    - Fixes the post-cooldown gap anomaly: last_hit_p is cleared to None
      when the tier-hit cooldown begins, so the first gap measured after
      cooldown lifts is always a fresh local observation rather than a
      potentially stale large distance from before cooldown.

    - Fixes the hardcoded lower-bound resume point in prime_yield_generator:
      the search now starts from START (default 5) rather than a hardcoded
      checkpoint that silently skips early p-space on cold runs. A
      --resume-from P flag is added for explicit checkpoint resumption.

GAP-SPIRAL ACCELERATOR (unchanged from v6)
-------------------------------------------

    1. Track the position of the most recent Tier-0 hit (a cubic Frobenius
       survivor). No aggregate class, no pooled sample.

    2. The moment a SECOND hit is found, the gap between them -- the actual,
       observed, local distance -- is used immediately at full strength:
       skip that entire distance forward. No waiting for statistical
       confidence.

    3. Each subsequent time this happens, the fraction of the newly observed
       gap that gets skipped contracts by a factor of phi (the golden ratio)
       -- 1.0, then 1/phi, then 1/phi^2 -- floored at 1/phi^2 (~38.2%) so
       the mechanism settles into a stable conservative orbit rather than
       decaying to zero.

    4. A verified higher-tier hit ($620 winner) triggers an immediate
       stand-down: the active skip is cancelled, last_hit_p is cleared, and
       skipping pauses for a short cooldown before the spiral resumes.

phi = (1 + sqrt(5)) / 2, derived from phi^2 = phi + 1.
"""

import sys
import time
import math
import os
from typing import List, Tuple, Dict, Any, Optional, Iterator


# =============================================================================
# CONFIGURATION
# =============================================================================

START = 5
LIMIT = 999_999_999

DISPLAY_INTERVAL = 0.50
SEGMENT_SIZE = 524288

SHORTCUT_LOG_LINES = 10
LINEAR_LOG_LINES = 8


# =============================================================================
# q MODULAR SCREEN
# =============================================================================

Q_SCREEN_PRIMES = (
    7, 11, 13, 17, 19, 23, 29, 31,
    37, 41, 43, 47
)


# =============================================================================
# PERSISTENT LEARNING
# =============================================================================

LOG_FILENAME = "cubic_frobenius_620_learning_v7.log"

LEARN_CHECKPOINT_EVERY = 50_000


# =============================================================================
# TERMINAL COLORS
# =============================================================================

GREEN   = "\033[92m"
RED     = "\033[91m"
YELLOW  = "\033[93m"
CYAN    = "\033[96m"
MAGENTA = "\033[95m"
RESET   = "\033[0m"

CLEAR_LINE = "\033[K"
MOVE_UP    = "\033[A"


# =============================================================================
# PERSISTENT LEARNING LOGGER
# =============================================================================

class LearningLogger:

    def __init__(self, filename: str = LOG_FILENAME):
        script_dir = os.path.dirname(os.path.abspath(__file__))
        self.path  = os.path.join(script_dir, filename)
        self.handle = open(self.path, "a", encoding="utf-8", buffering=1)
        self.closed = False

    def write(
        self,
        event:              str,
        n:                  Optional[int]   = None,
        p:                  Optional[int]   = None,
        q:                  Optional[int]   = None,
        reason:             Optional[str]   = None,
        observations:       Optional[int]   = None,
        useful:             Optional[int]   = None,
        skip_count:         Optional[int]   = None,
        start_bucket:       Optional[int]   = None,
        end_bucket:         Optional[int]   = None,
        total_observations: Optional[int]   = None,
        score:              Optional[float] = None,
        tier:               Optional[str]   = None,
    ) -> None:

        if self.closed:
            return

        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")

        fields = []
        for key, value in (
            ("n",                  n),
            ("p",                  p),
            ("q",                  q),
            ("observations",       observations),
            ("useful",             useful),
            ("skip_count",         skip_count),
            ("start_bucket",       start_bucket),
            ("end_bucket",         end_bucket),
            ("total_observations", total_observations),
            ("score",              score),
            ("tier",               tier),
            ("reason",             reason),
        ):
            if value is not None:
                fields.append(f"{key}={value}")

        self.handle.write(
            f"{timestamp} | [{event}] {' | '.join(fields)}\n"
        )

    def flush(self) -> None:
        if self.closed:
            return
        try:
            self.handle.flush()
        except Exception:
            pass

    def close(self) -> None:
        if self.closed:
            return
        try:
            self.handle.flush()
            self.handle.close()
        except Exception:
            pass
        self.closed = True

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        self.close()


# =============================================================================
# SMALL PRIME CACHE
# =============================================================================

SMALL_PRIME_POOL = [
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
    67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137,
    139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211,
    223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
    293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379,
    383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461,
    463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563,
    569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643,
    647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739,
    743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
    839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937,
    941, 947, 953, 967, 971, 977, 983, 991, 997,
]


# =============================================================================
# INTEGER HELPERS
# =============================================================================

def ext_gcd_int(a: int, b: int) -> Tuple[int, int, int]:
    x0, x1 = 1, 0
    y0, y1 = 0, 1
    while b != 0:
        q, a, b = a // b, b, a % b
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return a, x0, y0


def mod_inv_int(a: int, m: int) -> int:
    g, x, _ = ext_gcd_int(a, m)
    if g != 1:
        raise ValueError(g)
    return x % m


# =============================================================================
# POLYNOMIAL ARITHMETIC
# =============================================================================

def poly_clean(p: List[int]) -> List[int]:
    while p and p[-1] == 0:
        p.pop()
    return p


def poly_add(a: List[int], b: List[int], n: int) -> List[int]:
    res = [0] * max(len(a), len(b))
    for i in range(len(res)):
        ca = a[i] if i < len(a) else 0
        cb = b[i] if i < len(b) else 0
        res[i] = (ca + cb) % n
    return poly_clean(res)


def poly_sub(a: List[int], b: List[int], n: int) -> List[int]:
    res = [0] * max(len(a), len(b))
    for i in range(len(res)):
        ca = a[i] if i < len(a) else 0
        cb = b[i] if i < len(b) else 0
        res[i] = (ca - cb) % n
    return poly_clean(res)


def poly_mul(a: List[int], b: List[int], n: int) -> List[int]:
    if not a or not b:
        return []
    res = [0] * (len(a) + len(b) - 1)
    for i, ca in enumerate(a):
        if ca == 0:
            continue
        for j, cb in enumerate(b):
            if cb == 0:
                continue
            res[i + j] = (res[i + j] + ca * cb) % n
    return poly_clean(res)


def poly_make_monic(p: List[int], n: int) -> List[int]:
    p = poly_clean(p[:])
    if not p:
        return p
    lead = p[-1]
    if lead == 1:
        return p
    inv = mod_inv_int(lead, n)
    return [(c * inv) % n for c in p]


def poly_divmod(
    num: List[int], den: List[int], n: int
) -> Tuple[List[int], List[int]]:
    num = poly_clean(num[:])
    den = poly_clean(den[:])
    if not den:
        raise ZeroDivisionError("Polynomial division by zero.")
    if not num:
        return [], []
    if len(num) < len(den):
        return [], num
    quot = [0] * (len(num) - len(den) + 1)
    lead_inv = mod_inv_int(den[-1], n)
    while num and len(num) >= len(den):
        deg_diff = len(num) - len(den)
        q_coeff  = (num[-1] * lead_inv) % n
        quot[deg_diff] = q_coeff
        if q_coeff:
            for i, dc in enumerate(den):
                num[deg_diff + i] = (num[deg_diff + i] - q_coeff * dc) % n
        poly_clean(num)
    return poly_clean(quot), poly_clean(num)


def poly_gcd(a: List[int], b: List[int], n: int) -> List[int]:
    a = poly_clean(a[:])
    b = poly_clean(b[:])
    while b:
        _, r = poly_divmod(a, b, n)
        a, b = b, r
    if not a:
        return []
    return poly_make_monic(a, n)


def poly_powmod(
    base: List[int], exp: int, mod_poly: List[int], n: int
) -> List[int]:
    res  = [1]
    curr = poly_clean(base[:])
    while exp > 0:
        if exp & 1:
            res = poly_mul(res, curr, n)
            _, res = poly_divmod(res, mod_poly, n)
        exp >>= 1
        if exp:
            curr = poly_mul(curr, curr, n)
            _, curr = poly_divmod(curr, mod_poly, n)
    return res


def poly_eval_composition(
    outer: List[int], inner: List[int], mod_poly: List[int], n: int
) -> List[int]:
    res: List[int] = []
    curr_power = [1]
    for coeff in outer:
        if coeff != 0:
            term = [(c * coeff) % n for c in curr_power]
            res  = poly_add(res, term, n)
        curr_power = poly_mul(curr_power, inner, n)
        _, curr_power = poly_divmod(curr_power, mod_poly, n)
    if res:
        _, res = poly_divmod(res, mod_poly, n)
    return poly_clean(res)


# =============================================================================
# FROBENIUS AUDITOR
# =============================================================================

class FrobeniusAuditor:

    def __init__(self, n: int, poly_coeffs: List[int]):
        self.n   = n
        self.f0  = poly_clean([c % n for c in poly_coeffs])
        self.deg = len(self.f0) - 1

    def execute_audit(self) -> Dict[str, Any]:
        report: Dict[str, Any] = {
            "candidate":              self.n,
            "polynomial":             self.f0[:],
            "degree":                 self.deg,
            "preamble_passed":        False,
            "factorization_passed":   False,
            "frobenius_passed":       False,
            "composite_factor_found": None,
            "stage_failures":         [],
            "factors_discovered":     {},
        }

        g = math.gcd(self.n, 44)
        if g > 1 and g != self.n:
            report["composite_factor_found"] = g
            report["stage_failures"].append("PREAMBLE_GCD_FAULT")
            return report
        report["preamble_passed"] = True

        curr_f  = self.f0[:]
        factors: Dict[int, List[int]] = {}

        try:
            x_pow_n       = poly_powmod([0, 1], self.n, self.f0, self.n)
            current_x_pow = x_pow_n[:]

            for i in range(1, self.deg + 1):
                if len(curr_f) <= 1:
                    break
                g_x = poly_sub(current_x_pow, [0, 1], self.n)
                _, g_x_reduced = poly_divmod(g_x, curr_f, self.n)
                F_i = poly_gcd(g_x_reduced, curr_f, self.n)
                if len(F_i) > 1:
                    factors[i] = F_i
                    _, curr_f  = poly_divmod(curr_f, F_i, self.n)
                if i < self.deg:
                    current_x_pow = poly_eval_composition(
                        current_x_pow, x_pow_n, self.f0, self.n
                    )

            if len(curr_f) > 1:
                existing     = factors.get(self.deg, [])
                factors[self.deg] = poly_add(existing, curr_f, self.n)

        except ValueError as exc:
            report["composite_factor_found"] = int(exc.args[0])
            report["stage_failures"].append("FACTORIZATION_MODULAR_COLLAPSE")
            return report

        report["factors_discovered"] = {
            deg: coeffs
            for deg, coeffs in factors.items()
            if len(coeffs) > 1
        }

        total_deg = sum(len(poly) - 1 for poly in factors.values())
        if total_deg != self.deg:
            report["stage_failures"].append("INVALID_DEGREE_FIELDS")
            return report
        report["factorization_passed"] = True

        frobenius_verified = True
        try:
            for degree, F_i in factors.items():
                if len(F_i) <= 1:
                    continue
                _, x_n_reduced = poly_divmod(x_pow_n, F_i, self.n)
                composition_result = poly_clean(
                    poly_eval_composition(F_i, x_n_reduced, F_i, self.n)
                )
                if len(composition_result) > 0:
                    frobenius_verified = False
                    report["stage_failures"].append(
                        f"FROBENIUS_MAPPING_DEVIATION_DEG_{degree}"
                    )

        except ValueError as exc:
            report["composite_factor_found"] = int(exc.args[0])
            report["stage_failures"].append("FROBENIUS_STAGE_COLLAPSE")
            return report

        if frobenius_verified:
            report["frobenius_passed"] = True

        return report


# =============================================================================
# FAST PRIMALITY
# =============================================================================

def is_prime_fast(n: int) -> bool:
    if n < 2:
        return False
    for p in SMALL_PRIME_POOL:
        if n % p == 0:
            return n == p
    d, s = n - 1, 0
    while (d & 1) == 0:
        d >>= 1
        s += 1
    bases = (
        (2, 325, 9375, 28178, 450775, 9780504, 1795265022)
        if n < 18446744073709551616
        else (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37)
    )
    for a in bases:
        a %= n
        if a == 0:
            continue
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(s - 1):
            x = (x * x) % n
            if x == n - 1:
                break
        else:
            return False
    return True


# =============================================================================
# q MODULAR SCREEN
# =============================================================================

def q_small_prime_screen(p: int, q: int) -> bool:
    for r in Q_SCREEN_PRIMES:
        if q != r and q % r == 0:
            return False
    return True


# =============================================================================
# SEGMENTED PRIME GENERATOR
# =============================================================================

def base_primes_upto(limit: int) -> List[int]:
    if limit < 2:
        return []
    sieve = bytearray(b"\x01") * (limit + 1)
    sieve[0:2] = b"\x00\x00"
    root = math.isqrt(limit)
    for p in range(2, root + 1):
        if sieve[p]:
            start = p * p
            count = (limit - start) // p + 1
            sieve[start : limit + 1 : p] = b"\x00" * count
    return [i for i, v in enumerate(sieve) if v]


def prime_yield_generator(
    start_bound: int, end_bound: int
) -> Iterator[int]:
    if end_bound < start_bound:
        return
    base_limit  = math.isqrt(end_bound)
    small_primes = base_primes_upto(base_limit)
    low = max(2, start_bound)
    while low <= end_bound:
        high    = min(low + SEGMENT_SIZE - 1, end_bound)
        seg_len = high - low + 1
        sieve_block = bytearray(b"\x01") * seg_len
        for p in small_primes:
            if p * p > high:
                break
            start_idx = max(p * p, ((low + p - 1) // p) * p)
            if start_idx > high:
                continue
            offset = start_idx - low
            count  = (high - start_idx) // p + 1
            sieve_block[offset : offset + count * p : p] = b"\x00" * count
        for i, v in enumerate(sieve_block):
            if v:
                actual = low + i
                if actual > 1:
                    yield actual
        low += SEGMENT_SIZE


# =============================================================================
# FIBONACCI / LUCAS
# =============================================================================

def fibonacci_pair_mod_iterative(k: int, modulus: int) -> Tuple[int, int]:
    if modulus <= 0:
        raise ValueError("modulus must be positive")
    if k == 0:
        return 0, 1 % modulus
    a, b = 0, 1
    for bit_index in range(k.bit_length() - 1, -1, -1):
        c = (a * ((2 * b - a) % modulus)) % modulus
        d = (a * a + b * b) % modulus
        if (k >> bit_index) & 1:
            a, b = d, (c + d) % modulus
        else:
            a, b = c, d
    return a, b


def fibonacci_and_lucas_mod_n(n: int) -> Tuple[int, int]:
    f_k, f_k_plus_1 = fibonacci_pair_mod_iterative(n + 1, n)
    v_k = (2 * f_k_plus_1 - f_k) % n
    return f_k, v_k


# =============================================================================
# SMALL FACTOR RECOVERY
# =============================================================================

def factor_small(n: int) -> Optional[int]:
    if n % 2 == 0:
        return 2
    if n % 3 == 0:
        return 3
    d, step = 5, 2
    while d * d <= n:
        if n % d == 0:
            return d
        d += step
        step = 6 - step
    return None


# =============================================================================
# $620 VERIFICATION
# =============================================================================

def verify_620(n: int) -> Dict[str, Any]:
    prime    = is_prime_fast(n)
    mod5     = n % 5
    residue_ok      = mod5 in (2, 3)
    base2_residue   = None
    base2_ok        = False
    fib_residue     = None
    lucas_residue   = None
    fib_ok          = False

    if residue_ok:
        base2_residue = pow(2, n - 1, n)
        base2_ok      = base2_residue == 1
        if base2_ok:
            fib_residue, lucas_residue = fibonacci_and_lucas_mod_n(n)
            fib_ok = fib_residue == 0

    return {
        "candidate":         n,
        "prime":             prime,
        "composite":         not prime,
        "n_mod_5":           mod5,
        "residue_ok":        residue_ok,
        "base2_residue":     base2_residue,
        "base2_ok":          base2_ok,
        "fibonacci_residue": fib_residue,
        "lucas_residue":     lucas_residue,
        "fibonacci_ok":      fib_ok,
        "620_candidate":     (
            not prime and residue_ok and base2_ok and fib_ok
        ),
    }


# =============================================================================
# FACTOR FORM
# =============================================================================

def factor_form_coefficients(ratio_strat: str) -> Tuple[int, int]:
    if ratio_strat == "3p-2":
        return 3, -2
    if ratio_strat == "2p+1":
        return 2, 1
    if ratio_strat == "7p-6":
        return 7, -6
    raise ValueError("Unknown ratio strategy.")


def candidate_mod5_classes(ratio_strat: str) -> List[int]:
    a, b   = factor_form_coefficients(ratio_strat)
    valid  = []
    for r in range(1, 5):
        q_mod = (a * r + b) % 5
        n_mod = (r * q_mod) % 5
        if n_mod in (2, 3):
            valid.append(r)
    return valid


def strategy_is_mod5_impossible(ratio_strat: str) -> bool:
    return len(candidate_mod5_classes(ratio_strat)) == 0


def max_p_for_strategy(limit_upper: int, ratio_strat: str) -> int:
    if limit_upper <= 0:
        return 0
    a, b = factor_form_coefficients(ratio_strat)
    discriminant = b * b + 4 * a * limit_upper
    max_p = max(2, (math.isqrt(max(0, discriminant)) - b) // (2 * a) + 2)
    while max_p > 0 and max_p * (a * max_p + b) >= limit_upper:
        max_p -= 1
    while (max_p + 1) > 0 and (max_p + 1) * (a * (max_p + 1) + b) < limit_upper:
        max_p += 1
    return max_p


# =============================================================================
# LOG FIELD RECOVERY
# =============================================================================

def parse_log_fields(line: str) -> Dict[str, Any]:
    result: Dict[str, Any] = {}
    if "|" not in line:
        return result
    parts = [x.strip() for x in line.split("|")]
    for part in parts:
        if part.startswith("["):
            close = part.find("]")
            if close != -1:
                part = part[close + 1:].strip()
        if "=" not in part:
            continue
        key, value = part.split("=", 1)
        key   = key.strip()
        value = value.strip()
        if key in (
            "n", "p", "q", "observations", "useful",
            "skip_count", "start_bucket", "end_bucket", "total_observations",
        ):
            try:
                result[key] = int(value)
            except ValueError:
                pass
        elif key in ("score",):
            try:
                result[key] = float(value)
            except ValueError:
                pass
        elif key in ("reason", "tier"):
            result[key] = value
    return result


# =============================================================================
# QUADRATIC FIBONACCI/LUCAS CLOSURE
# =============================================================================

def quadratic_closure_audit(n: int) -> Dict[str, Any]:
    """
    Axis A: L_n == 1 (mod n)
    Axis B: F_n follows the discriminant-5 Frobenius branch
    Axis C: A^n matches the corresponding full 2x2 Frobenius matrix
    """
    result: Dict[str, Any] = {
        "candidate":       n,
        "n_mod_5":         n % 5,
        "fibonacci":       None,
        "lucas":           None,
        "axis_a":          False,
        "axis_b":          False,
        "axis_c":          False,
        "matrix":          None,
        "expected_matrix": None,
        "passed":          False,
        "reason":          "",
    }

    if n < 2:
        result["reason"] = "n<2"
        return result

    f_n, f_np1 = fibonacci_pair_mod_iterative(n, n)
    lucas_n    = (2 * f_np1 - f_n) % n
    f_nm1      = (f_np1 - f_n) % n

    result["fibonacci"] = f_n
    result["lucas"]     = lucas_n

    axis_a = lucas_n == 1 % n
    result["axis_a"] = axis_a

    mod5 = n % 5
    if n == 5:
        expected_f = 0
    elif mod5 in (1, 4):
        expected_f = 1 % n
    elif mod5 in (2, 3):
        expected_f = (-1) % n
    else:
        expected_f = None

    axis_b = expected_f is not None and f_n == expected_f
    result["axis_b"] = axis_b

    actual_matrix = ((f_np1, f_n), (f_n, f_nm1))

    if n == 5:
        expected_matrix = ((3 % n, 0), (0, 3 % n))
    elif mod5 in (1, 4):
        expected_matrix = ((1 % n, 1 % n), (1 % n, 0))
    elif mod5 in (2, 3):
        expected_matrix = ((0, (-1) % n), ((-1) % n, 1 % n))
    else:
        expected_matrix = None

    axis_c = expected_matrix is not None and actual_matrix == expected_matrix

    result["matrix"]          = actual_matrix
    result["expected_matrix"] = expected_matrix
    result["axis_c"]          = axis_c
    result["passed"]          = axis_a and axis_b and axis_c

    if result["passed"]:
        result["reason"] = "A+B+C"
    elif not axis_a:
        result["reason"] = "AXIS_A"
    elif not axis_b:
        result["reason"] = "AXIS_B"
    else:
        result["reason"] = "AXIS_C"

    return result


# =============================================================================
# BINARY / sqrt(2) FROBENIUS CLOSURE  (discriminant 8, P=-2, Q=-1)
# =============================================================================

BINARY_P = -2
BINARY_Q = -1
BINARY_D = BINARY_P * BINARY_P - 4 * BINARY_Q   # = 8


def jacobi_symbol_int(a: int, n: int) -> int:
    if n <= 0 or (n & 1) == 0:
        raise ValueError("Jacobi modulus must be positive and odd")
    a %= n
    result = 1
    while a:
        while (a & 1) == 0:
            a >>= 1
            if (n & 7) in (3, 5):
                result = -result
        a, n = n, a
        if (a & 3) == 3 and (n & 3) == 3:
            result = -result
        a %= n
    return result if n == 1 else 0


def _lucas_uv_mod(P: int, Q: int, n: int, k: int) -> Tuple[int, int]:
    """Generic companion-matrix exponentiation for Lucas sequences."""
    if n <= 0 or k < 0:
        raise ValueError("invalid modulus or index")
    r00, r01, r10, r11 = 1 % n, 0, 0, 1 % n
    a00, a01, a10, a11 = P % n, (-Q) % n, 1 % n, 0
    e = k
    while e:
        if e & 1:
            r00, r01, r10, r11 = (
                (r00 * a00 + r01 * a10) % n,
                (r00 * a01 + r01 * a11) % n,
                (r10 * a00 + r11 * a10) % n,
                (r10 * a01 + r11 * a11) % n,
            )
        a00, a01, a10, a11 = (
            (a00 * a00 + a01 * a10) % n,
            (a00 * a01 + a01 * a11) % n,
            (a10 * a00 + a11 * a10) % n,
            (a10 * a01 + a11 * a11) % n,
        )
        e >>= 1
    return r10, (r00 + r11) % n


def binary_quadratic_vantage_audit(n: int) -> Dict[str, Any]:
    result: Dict[str, Any] = {
        "candidate":  n,
        "jacobi_8":   None,
        "index":      None,
        "u":          None,
        "v":          None,
        "expected_v": None,
        "u_pass":     False,
        "v_pass":     False,
        "passed":     False,
        "reason":     "",
    }
    if n <= 2 or (n & 1) == 0:
        result["reason"] = "NON_ODD_MODULUS"
        return result
    if math.gcd(n, BINARY_D) != 1:
        result["reason"] = "DISCRIMINANT_COLLAPSE"
        return result
    chi        = jacobi_symbol_int(BINARY_D, n)
    k          = n - chi
    u, v       = _lucas_uv_mod(BINARY_P, BINARY_Q, n, k)
    expected_v = 2 % n if chi == 1 else (2 * BINARY_Q) % n
    result.update({
        "jacobi_8":   chi,
        "index":      k,
        "u":          u,
        "v":          v,
        "expected_v": expected_v,
        "u_pass":     u == 0,
        "v_pass":     v == expected_v,
    })
    result["passed"] = result["u_pass"] and result["v_pass"]
    if result["passed"]:
        result["reason"] = "BINARY_U+V"
    elif not result["u_pass"]:
        result["reason"] = "BINARY_U"
    else:
        result["reason"] = "BINARY_V"
    return result


# =============================================================================
# TRINARY / sqrt(13) FROBENIUS CLOSURE  (discriminant 13, P=-3, Q=-1)
# =============================================================================

TRINARY_P = -3
TRINARY_Q = -1
TRINARY_D = TRINARY_P * TRINARY_P - 4 * TRINARY_Q   # = 13


def trinary_quadratic_vantage_audit(n: int) -> Dict[str, Any]:
    result: Dict[str, Any] = {
        "candidate":   n,
        "jacobi_13":   None,
        "index":       None,
        "u":           None,
        "v":           None,
        "expected_v":  None,
        "u_pass":      False,
        "v_pass":      False,
        "passed":      False,
        "reason":      "",
    }
    if n <= 2 or (n & 1) == 0:
        result["reason"] = "NON_ODD_MODULUS"
        return result
    g = math.gcd(n, TRINARY_D)
    if g != 1:
        result["jacobi_13"] = 0
        if n == TRINARY_D:
            result["passed"] = True
            result["reason"] = "PRIME_DISCRIMINANT_BOUNDARY"
        else:
            result["reason"] = "DISCRIMINANT_FACTOR_13"
        return result
    chi        = jacobi_symbol_int(TRINARY_D, n)
    k          = n - chi
    u, v       = _lucas_uv_mod(TRINARY_P, TRINARY_Q, n, k)
    expected_v = 2 % n if chi == 1 else (2 * TRINARY_Q) % n
    result.update({
        "jacobi_13":   chi,
        "index":       k,
        "u":           u,
        "v":           v,
        "expected_v":  expected_v,
        "u_pass":      u == 0,
        "v_pass":      v == expected_v,
    })
    result["passed"] = result["u_pass"] and result["v_pass"]
    if result["passed"]:
        result["reason"] = "TRINARY_U+V"
    elif not result["u_pass"]:
        result["reason"] = "TRINARY_U"
    else:
        result["reason"] = "TRINARY_V"
    return result


# =============================================================================
# GAP-SPIRAL LEARNED ACCELERATOR  (v6, unchanged)
# =============================================================================

class GapSpiralController:
    """
    Heuristic acceleration layer -- v6 gap-spiral, carried forward to v7.

    Acts on the raw stream of Tier-0 hits (cubic Frobenius survivors):

        1. Track the position of the most recent Tier-0 hit.

        2. The moment a second hit is found, the gap between them is used
           immediately at full strength (fraction = 1.0 of the gap).

        3. Each subsequent gap is used at a fraction that contracts by 1/phi
           per cycle, floored at 1/phi^2 (~38.2%).

        4. A verified $620 winner triggers stand-down: the active skip is
           cancelled, last_hit_p is cleared (v7 fix), and skipping pauses
           for TIER_HIT_COOLDOWN_HITS before the spiral resumes at its floor.

    phi = (1 + sqrt(5)) / 2, derived from phi^2 = phi + 1.
    """

    PHI = (1.0 + math.sqrt(5.0)) / 2.0
    SPIRAL_FLOOR_CYCLE     = 2
    TIER_HIT_COOLDOWN_HITS = 5

    def __init__(self, logger: LearningLogger, max_p: int):
        self.logger  = logger
        self.max_p   = max(1, max_p)

        self.last_hit_p:         Optional[int] = None
        self.cycle_index:        int           = 0
        self.active_skip_start:  Optional[int] = None
        self.active_skip_end:    Optional[int] = None
        self.cooldown_remaining: int           = 0

        self.total_observations: int   = 0
        self.total_useful:       int   = 0
        self.total_skipped:      int   = 0
        self.arm_events:         int   = 0
        self.tier_hit_counts:    Dict[str, int] = {}

        self.last_gap:       int   = 0
        self.last_fraction:  float = 0.0
        self.last_skip_size: int   = 0

        self._recover()

    def _recover(self) -> None:
        path = self.logger.path
        if not os.path.exists(path):
            return
        try:
            with open(path, "r", encoding="utf-8") as fh:
                for line in fh:
                    if "[GAP CHECKPOINT]" in line:
                        fields = parse_log_fields(line)
                        if "p" in fields:
                            self.last_hit_p = fields["p"]
                        if "skip_count" in fields:
                            self.cycle_index = fields["skip_count"]
                        if "total_observations" in fields:
                            self.total_observations = fields["total_observations"]
                        if "useful" in fields:
                            self.total_useful = fields["useful"]
                    elif "[GAP TIER HIT]" in line:
                        fields = parse_log_fields(line)
                        tier   = fields.get("tier", "UNKNOWN")
                        self.tier_hit_counts[tier] = (
                            self.tier_hit_counts.get(tier, 0) + 1
                        )
        except OSError:
            pass

    def checkpoint(self) -> None:
        self.logger.write(
            "GAP CHECKPOINT",
            p=self.last_hit_p,
            skip_count=self.cycle_index,
            total_observations=self.total_observations,
            useful=self.total_useful,
            reason="PERIODIC",
        )
        self.logger.flush()

    @property
    def current_fraction(self) -> float:
        return self.PHI ** (-min(self.cycle_index, self.SPIRAL_FLOOR_CYCLE))

    def decide_skip(self, p: int) -> Tuple[bool, str]:
        if self.active_skip_start is not None and self.active_skip_end is not None:
            if p <= self.active_skip_end:
                self.total_skipped += 1
                return True, "GAP_SPIRAL_SKIP"
            self.active_skip_start = None
            self.active_skip_end   = None
        return False, "EVALUATE"

    def observe(self, p: int, useful: bool) -> None:
        self.total_observations += 1
        if useful:
            self.total_useful += 1

        if self.cooldown_remaining > 0:
            self.cooldown_remaining -= 1
            # last_hit_p was already cleared when cooldown started (v7 fix).
            if self.total_observations % LEARN_CHECKPOINT_EVERY == 0:
                self.checkpoint()
            return

        if useful:
            if self.last_hit_p is not None:
                gap      = p - self.last_hit_p
                fraction = self.current_fraction
                skip_size = round(fraction * gap)

                self.last_gap       = gap
                self.last_fraction  = fraction
                self.last_skip_size = skip_size

                if skip_size > 0:
                    self.active_skip_start = p + 1
                    self.active_skip_end   = p + skip_size
                    self.arm_events       += 1

                    self.logger.write(
                        "GAP ARM",
                        p=p,
                        skip_count=skip_size,
                        score=fraction,
                        observations=gap,
                        start_bucket=self.active_skip_start,
                        end_bucket=self.active_skip_end,
                        reason=f"CYCLE_{self.cycle_index}",
                    )

                self.cycle_index += 1

            self.last_hit_p = p

        if self.total_observations % LEARN_CHECKPOINT_EVERY == 0:
            self.checkpoint()

    def record_tier_hit(self, p: int, tier: str) -> None:
        self.tier_hit_counts[tier] = self.tier_hit_counts.get(tier, 0) + 1
        self.active_skip_start  = None
        self.active_skip_end    = None
        self.last_hit_p         = None   # v7 fix: clear so post-cooldown gap is fresh
        self.cooldown_remaining = self.TIER_HIT_COOLDOWN_HITS

        self.logger.write(
            "GAP TIER HIT",
            p=p,
            tier=tier,
            observations=self.tier_hit_counts[tier],
            reason="COOLDOWN_TRIGGERED",
        )

        if tier == "$620" and self.tier_hit_counts[tier] == 2:
            self.logger.write(
                "LEARN MILESTONE",
                p=p,
                reason="GOLD_STANDARD_TWO_DATA_POINTS",
            )

    @property
    def global_yield(self) -> float:
        if self.total_observations <= 0:
            return 0.0
        return self.total_useful / self.total_observations

    def status_text(self) -> str:
        if self.cooldown_remaining > 0:
            return f"COOLDOWN ({self.cooldown_remaining} hits left)"
        if self.active_skip_start is not None and self.active_skip_end is not None:
            return (
                f"SKIPPING {self.active_skip_start:,}-{self.active_skip_end:,} "
                f"(cycle {self.cycle_index}, {self.current_fraction * 100:.1f}%)"
            )
        return (
            f"WATCHING (cycle {self.cycle_index}, "
            f"next {self.current_fraction * 100:.1f}%, "
            f"yield={self.global_yield * 100:.3f}%)"
        )

    def summary(self) -> Dict[str, Any]:
        return {
            "total_observations": self.total_observations,
            "total_useful":       self.total_useful,
            "global_yield":       self.global_yield,
            "total_skipped":      self.total_skipped,
            "arm_events":         self.arm_events,
            "cycle_index":        self.cycle_index,
            "current_fraction":   self.current_fraction,
            "last_gap":           self.last_gap,
            "last_skip_size":     self.last_skip_size,
            "tier_hits":          dict(self.tier_hit_counts),
        }


# =============================================================================
# TERMINAL DASHBOARD
# =============================================================================

class TerminalDashboard:

    def __init__(self, title_text: str, log_window_size: int = 8):
        self.title_text   = title_text
        self.log_size     = log_window_size
        self.logs:        List[str] = []
        self.first_render = True
        # Log window + border rows + stat rows (no Poly-2 rows now)
        self.total_lines  = self.log_size + 33

    def add_log(self, text: str) -> None:
        self.logs.append(text)
        if len(self.logs) > self.log_size:
            self.logs.pop(0)

    @staticmethod
    def _rate(count: int, elapsed: float) -> str:
        if elapsed <= 0:
            return "0.00/s"
        return f"{count / elapsed:,.2f}/s"

    @staticmethod
    def _percent(current: int, maximum: int) -> str:
        if maximum <= 0:
            return "0.000%"
        return f"{current / maximum * 100.0:,.3f}%"

    def refresh(
        self,
        elapsed:                  float,
        pairs:                    int,
        cubic_survivors:          int,
        sieve_status:             str,
        hits:                     int,
        seed_p:                   int,
        strat_text:               str,
        max_p:                    int,
        current_q:                Optional[int] = None,
        current_candidate:        Optional[int] = None,
        phase:                    str           = "SEARCHING",
        q_tested:                 int           = 0,
        q_rejected:               int           = 0,
        q_screen_rejected:        int           = 0,
        residue_rejected:         int           = 0,
        fermat_rejected:          int           = 0,
        quadratic_attempts:       int           = 0,
        quadratic_survivors:      int           = 0,
        quadratic_rejected:       int           = 0,
        binary_vantage_survivors: int           = 0,
        binary_vantage_rejected:  int           = 0,
        trinary_vantage_survivors:int           = 0,
        trinary_vantage_rejected: int           = 0,
        poly1_attempts:           int           = 0,
        poly1_rejected:           int           = 0,
        learned_status:           str           = "OFF",
        learned_cycle_index:      int           = 0,
        learned_fraction:         float         = 0.0,
        learned_last_gap:         int           = 0,
        learned_last_skip_size:   int           = 0,
        learned_total_skipped:    int           = 0,
        learned_arm_events:       int           = 0,
        learned_observations:     int           = 0,
        learned_yield:            float         = 0.0,
        learned_tier_hits:        str           = "-",
    ) -> None:

        if not self.first_render:
            sys.stdout.write(MOVE_UP * self.total_lines)
        else:
            print("\n" * (self.total_lines - 1))
            sys.stdout.write(MOVE_UP * (self.total_lines - 1))
            self.first_render = False

        for i in range(self.log_size):
            sys.stdout.write(CLEAR_LINE)
            print(self.logs[i] if i < len(self.logs) else "")

        seed_text = f"p = {seed_p:,}" if seed_p is not None else "-"
        q_text    = f"q = {current_q:,}" if current_q is not None else "-"
        cand_text = f"n = {current_candidate:,}" if current_candidate is not None else "-"
        prog_text = (
            self._percent(seed_p, max_p) if seed_p is not None else "0.000%"
        )

        width = 83

        def row(label: str, value: str, color: str = "") -> None:
            content = f"{label:<34}{value:>47}"
            print(f"║ {color}{content}{RESET} ║")

        print("╔" + "═" * width + "╗")
        print(f"║ {CYAN}{self.title_text:^{width - 2}}{RESET} ║")
        print("╠" + "═" * width + "╣")

        row("Elapsed Runtime",                f"{elapsed:,.3f} s")
        row("Search Phase",                   phase, YELLOW)
        row("Factor Strategy",                strat_text, CYAN)
        row("Prime Seed",                     seed_text)
        row("q",                              q_text)
        row("Candidate n",                    cand_text)
        row("p Search Progress",              f"{prog_text} / max p = {max_p:,}")
        row("Candidate Pairs Tested",         f"{pairs:,} ({self._rate(pairs, elapsed)})")
        row("q Modular-Sieve Rejects",        f"{q_screen_rejected:,}")
        row("q Primality Tests",              f"{q_tested:,}")
        row("q Rejected",                     f"{q_rejected:,}")
        row("Mod-5 Rejected",                 f"{residue_rejected:,}")
        row("Factor-Reduced Fermat Rejects",  f"{fermat_rejected:,}")
        row("Quadratic Closure Attempts",     f"{quadratic_attempts:,}")
        row("Quadratic Closure Survivors",    f"{quadratic_survivors:,}",
            GREEN if quadratic_survivors else "")
        row("Quadratic Closure Rejections",   f"{quadratic_rejected:,}")
        row("Binary Vantage Survivors",       f"{binary_vantage_survivors:,}",
            GREEN if binary_vantage_survivors else "")
        row("Binary Vantage Rejections",      f"{binary_vantage_rejected:,}")
        row("Trinary Vantage Survivors",      f"{trinary_vantage_survivors:,}",
            GREEN if trinary_vantage_survivors else "")
        row("Trinary Vantage Rejections",     f"{trinary_vantage_rejected:,}")
        row("Cubic Polynomial Attempts",      f"{poly1_attempts:,}")
        row("Cubic Survivors",                f"{cubic_survivors:,}",
            GREEN if cubic_survivors else "")
        row("Cubic Rejections",               f"{poly1_attempts - cubic_survivors:,}")
        row("Learned Accelerator",            learned_status,
            MAGENTA if "SKIPPING" in learned_status else CYAN)
        row("Learned Spiral Cycle / Fraction",
            f"{learned_cycle_index} / {learned_fraction * 100:.1f}%")
        row("Learned Last Gap / Skip Size",
            f"{learned_last_gap:,} / {learned_last_skip_size:,}")
        row("Learned Arm Events",             f"{learned_arm_events:,}")
        row("Learned Candidates Skipped",     f"{learned_total_skipped:,}")
        row("Learned Tier Hits ($620)",       learned_tier_hits)
        row("Learned Observations",           f"{learned_observations:,}")
        row("Learned Cubic Yield",            f"{learned_yield * 100.0:,.3f}%")
        row("Validated $620 Hits",            f"{hits:,}",
            GREEN if hits else "")

        print("╚" + "═" * width + "╝")
        sys.stdout.flush()


# =============================================================================
# $620 REPORT
# =============================================================================

def print_620_report_to_string(
    n: int,
    silverware: Dict[str, Any],
    witness: Optional[int] = None,
) -> str:
    if witness is None:
        witness = factor_small(n)
    if witness and not silverware["prime"]:
        witness_str = f" [{witness}x{n // witness}]"
    else:
        witness_str = ""
    status = (
        f"{GREEN}WINNER{RESET}"
        if silverware["620_candidate"]
        else "REJECTED"
    )
    return (
        f"[Hit] n={n:,} | "
        f"V={silverware['lucas_residue']} | "
        f"Status: {status}{witness_str}"
    )


# =============================================================================
# EXPLICIT INSPECTION
# =============================================================================

def inspect_candidates(values: List[int]) -> None:
    for n in values:
        print("\n" + "=" * 79)
        print(f"UNIFIED INTERPRETIVE LOG TRACE: {n}")
        print("=" * 79)

        six20 = verify_620(n)

        auditor    = FrobeniusAuditor(n, [-1, -1, -1, 1])
        audit_res  = auditor.execute_audit()

        print(f"Primality Classification   : {'PRIME' if six20['prime'] else 'COMPOSITE'}")
        print(f"Modulus Congruence (n%5)   : {six20['n_mod_5']} (Target is 2 or 3)")
        print(f"±2 mod 5 Boundary Status   : {'PASS' if six20['residue_ok'] else 'FAIL'}")
        print(f"2^(n-1) Modulo Remainder   : {six20['base2_residue']}")
        print(f"Base-2 Fermat Test Result  : {'PASS' if six20['base2_ok'] else 'FAIL'}")
        print(f"F_(n+1) Modulo Remainder   : {six20['fibonacci_residue']}")
        print(f"V_(n+1) Lucas Remainder    : {six20['lucas_residue']}")
        print(f"*** $620 STATUS             : "
              f"{'[!!!] WINNING CANDIDATE' if six20['620_candidate'] else 'REJECTED'}")

        print("\nQUADRATIC FIBONACCI/LUCAS CLOSURE")
        print("-" * 31)
        quadratic = quadratic_closure_audit(n)
        print(f"Axis A: L_n ≡ 1 (mod n) : {'PASS' if quadratic['axis_a'] else 'FAIL'}")
        print(f"Axis B: F_n branch       : {'PASS' if quadratic['axis_b'] else 'FAIL'}")
        print(f"Axis C: A^n matrix       : {'PASS' if quadratic['axis_c'] else 'FAIL'}")
        print(f"F_n modulo n              : {quadratic['fibonacci']}")
        print(f"L_n modulo n              : {quadratic['lucas']}")
        print(f"Quadratic closure status  : {'PASSED' if quadratic['passed'] else 'REJECTED'}")

        binary = binary_quadratic_vantage_audit(n)
        print("\nBINARY / sqrt(2) QUADRATIC VANTAGE")
        print("-" * 31)
        print(f"U_k == 0                 : {'PASS' if binary['u_pass'] else 'FAIL'}")
        print(f"V_k Frobenius branch     : {'PASS' if binary['v_pass'] else 'FAIL'}")
        print(f"Binary vantage status    : {'PASSED' if binary['passed'] else 'REJECTED'}")

        trinary = trinary_quadratic_vantage_audit(n)
        print("\nTRINARY / sqrt(13) QUADRATIC VANTAGE")
        print("-" * 31)
        print(f"U_k == 0                 : {'PASS' if trinary['u_pass'] else 'FAIL'}")
        print(f"V_k Frobenius branch     : {'PASS' if trinary['v_pass'] else 'FAIL'}")
        print(f"Trinary vantage status   : {'PASSED' if trinary['passed'] else 'REJECTED'}")
        print(f"Trinary diagnostic       : {trinary['reason']}")

        print("\nGRANTHAM EXTENSION LAYER AUDIT")
        print("-" * 31)
        print(f"Stage 1 Core Preamble       : {'PASSED' if audit_res['preamble_passed'] else 'FAILED'}")
        print(f"Stage 2 Structural Splitting: {'PASSED' if audit_res['factorization_passed'] else 'FAILED'}")
        print(f"Stage 3 Frobenius Mapping  : {'PASSED' if audit_res['frobenius_passed'] else 'FAILED'}")
        if audit_res["composite_factor_found"]:
            print(f"Factor exposed by collapse : {audit_res['composite_factor_found']}")
        if audit_res["stage_failures"]:
            print("Audit diagnostics:")
            for failure in audit_res["stage_failures"]:
                print(f"  - {failure}")


# =============================================================================
# CORE SEARCH
# =============================================================================

def _learned_tier_hits_text(learned: GapSpiralController) -> str:
    if not learned.tier_hit_counts:
        return "-"
    return ", ".join(
        f"{tier}={count}"
        for tier, count in sorted(learned.tier_hit_counts.items())
    )


def search_shortcut_space(
    limit_upper:  int,
    ratio_strat:  str  = "3p-2",
    learned_mode: bool = False,
    resume_from:  int  = START,
) -> List[int]:

    valid_strategies = {"3p-2", "2p+1", "7p-6"}
    if ratio_strat not in valid_strategies:
        raise ValueError("Unknown ratio strategy. Use 3p-2, 2p+1, or 7p-6.")

    max_p  = max_p_for_strategy(limit_upper, ratio_strat)
    logger = LearningLogger()
    learned = GapSpiralController(logger, max_p)

    title = (
        "GAP-SPIRAL SHORTCUT PIPELINE"
        if learned_mode
        else "EXACT MATHEMATICAL SHORTCUT PIPELINE"
    )

    print()
    print(f"{CYAN}Learning log:{RESET} {logger.path}")

    if learned_mode:
        print(
            f"{YELLOW}WARNING: LEARNED MODE IS HEURISTIC. "
            "It may skip a mathematically valid candidate.{RESET}"
        )
        print(
            f"{YELLOW}"
            "First gap used at full distance; each subsequent gap contracts "
            f"by 1/phi per cycle, floored at 1/phi^2 "
            f"({GapSpiralController.PHI ** -GapSpiralController.SPIRAL_FLOOR_CYCLE * 100:.1f}%). "
            f"phi = {GapSpiralController.PHI:.6f}.{RESET}"
        )
    print()

    ui = TerminalDashboard(title, log_window_size=SHORTCUT_LOG_LINES)
    t0 = time.monotonic()

    checked_pairs             = 0
    q_tested                  = 0
    q_rejected                = 0
    q_screen_rejected         = 0
    residue_rejected          = 0
    fermat_rejected           = 0
    quadratic_attempts        = 0
    quadratic_survivors       = 0
    quadratic_rejected        = 0
    binary_vantage_survivors  = 0
    binary_vantage_rejected   = 0
    trinary_vantage_survivors = 0
    trinary_vantage_rejected  = 0
    poly1_attempts            = 0
    poly1_survivors           = 0

    true_620_hits: List[int] = []

    exact_classes = candidate_mod5_classes(ratio_strat)
    if exact_classes:
        sieve_status_str = (
            f"EXACT [p ≡ {exact_classes[0]} mod 5]"
            if len(exact_classes) == 1
            else f"EXACT [p mod 5 ∈ {exact_classes}]"
        )
    else:
        sieve_status_str = "EXACT [NO p>5 CLASS]"

    current_p:         int           = 0
    current_q:         Optional[int] = None
    current_candidate: Optional[int] = None
    last_display:      float         = 0.0

    def learned_kwargs() -> Dict[str, Any]:
        return {
            "learned_status":         learned.status_text() if learned_mode else "OFF",
            "learned_cycle_index":    learned.cycle_index,
            "learned_fraction":       learned.current_fraction,
            "learned_last_gap":       learned.last_gap,
            "learned_last_skip_size": learned.last_skip_size,
            "learned_total_skipped":  learned.total_skipped,
            "learned_arm_events":     learned.arm_events,
            "learned_observations":   learned.total_observations,
            "learned_yield":          learned.global_yield,
            "learned_tier_hits":      _learned_tier_hits_text(learned),
        }

    ui.add_log(f"[Init] Limit = {limit_upper:,}")
    ui.add_log(f"[Init] Ratio = {ratio_strat}")
    ui.add_log(f"[Init] Max p = {max_p:,}")
    ui.add_log(f"[Init] Resume from p = {resume_from:,}")
    if learned_mode:
        ui.add_log("[Init] Learned accelerator = ENABLED (v6 gap-spiral)")
        ui.add_log(
            f"[Init] Recovered {learned.total_observations:,} observations, "
            f"cycle_index={learned.cycle_index}, "
            f"last_hit_p={learned.last_hit_p}"
        )
    else:
        ui.add_log("[Init] Learned accelerator = DISABLED")

    ui.refresh(
        0.0, checked_pairs, poly1_survivors,
        sieve_status_str, 0, current_p, ratio_strat, max_p,
        phase="INITIALIZING", **learned_kwargs(),
    )
    last_display = time.monotonic()

    if strategy_is_mod5_impossible(ratio_strat):
        ui.add_log(
            f"{YELLOW}[Exact Skip] {ratio_strat} has no valid p mod 5 class.{RESET}"
        )
        elapsed = time.monotonic() - t0
        ui.refresh(
            elapsed, 0, 0, sieve_status_str, 0, 5, ratio_strat, max_p,
            phase="EXACTLY EMPTY", **learned_kwargs(),
        )
        logger.close()
        print()
        print("SHORTCUT SEARCH COMPLETE")
        print(f"Strategy                 : {ratio_strat}")
        print("Mathematical candidate space: EMPTY")
        return []

    a, b = factor_form_coefficients(ratio_strat)

    prime_stream = prime_yield_generator(max(START, resume_from), max_p)

    try:
        for p in prime_stream:
            current_p = p

            if p == 3:
                continue

            # Exact mod-5 sieve on p
            if p > 5 and p % 5 not in exact_classes:
                continue

            # Gap-spiral heuristic skip
            if learned_mode:
                skip, _ = learned.decide_skip(p)
                if skip:
                    continue

            # Factor pair
            q = a * p + b
            current_q = q
            candidate = p * q
            current_candidate = candidate

            if candidate >= limit_upper:
                break

            checked_pairs += 1

            now = time.monotonic()
            if now - last_display >= DISPLAY_INTERVAL:
                ui.refresh(
                    now - t0, checked_pairs, poly1_survivors,
                    sieve_status_str, len(true_620_hits),
                    p, ratio_strat, max_p,
                    current_q=q, current_candidate=candidate,
                    phase="TESTING q",
                    q_tested=q_tested, q_rejected=q_rejected,
                    q_screen_rejected=q_screen_rejected,
                    residue_rejected=residue_rejected,
                    fermat_rejected=fermat_rejected,
                    quadratic_attempts=quadratic_attempts,
                    quadratic_survivors=quadratic_survivors,
                    quadratic_rejected=quadratic_rejected,
                    binary_vantage_survivors=binary_vantage_survivors,
                    binary_vantage_rejected=binary_vantage_rejected,
                    trinary_vantage_survivors=trinary_vantage_survivors,
                    trinary_vantage_rejected=trinary_vantage_rejected,
                    poly1_attempts=poly1_attempts,
                    poly1_rejected=poly1_survivors - poly1_attempts,
                    **learned_kwargs(),
                )
                last_display = now

            # q modular sieve
            if not q_small_prime_screen(p, q):
                q_screen_rejected += 1
                continue

            # q primality
            q_tested += 1
            if not is_prime_fast(q):
                q_rejected += 1
                continue

            # Candidate mod 5
            if candidate % 5 not in (2, 3):
                residue_rejected += 1
                continue

            # Fermat mod p
            if pow(2, (candidate - 1) % (p - 1), p) != 1:
                fermat_rejected += 1
                continue

            # Fermat mod q
            if pow(2, (candidate - 1) % (q - 1), q) != 1:
                fermat_rejected += 1
                continue

            # Quadratic Fibonacci/Lucas closure (discriminant 5)
            quadratic_attempts += 1
            quadratic = quadratic_closure_audit(candidate)
            if not quadratic["passed"]:
                quadratic_rejected += 1
                continue
            quadratic_survivors += 1

            # Binary vantage (discriminant 8)
            binary_vantage = binary_quadratic_vantage_audit(candidate)
            if not binary_vantage["passed"]:
                binary_vantage_rejected += 1
                ui.add_log(
                    f"[Binary Vantage Reject] n={candidate:,} "
                    f"reason={binary_vantage['reason']} "
                    f"U={binary_vantage['u']} V={binary_vantage['v']} "
                    f"expectedV={binary_vantage['expected_v']}"
                )
                continue
            binary_vantage_survivors += 1

            # Trinary vantage (discriminant 13)
            trinary_vantage = trinary_quadratic_vantage_audit(candidate)
            if not trinary_vantage["passed"]:
                trinary_vantage_rejected += 1
                ui.add_log(
                    f"[Trinary Vantage Reject] n={candidate:,} "
                    f"reason={trinary_vantage['reason']} "
                    f"U={trinary_vantage['u']} V={trinary_vantage['v']} "
                    f"expectedV={trinary_vantage['expected_v']}"
                )
                continue
            trinary_vantage_survivors += 1

            ui.add_log(
                f"[All 3 Vantages Survived] n={candidate:,} "
                f"F={quadratic['fibonacci']} L={quadratic['lucas']} "
                f"U2={binary_vantage['u']} V2={binary_vantage['v']} "
                f"U3={trinary_vantage['u']} V3={trinary_vantage['v']}"
            )

            # Cubic Frobenius  (tribonacci: x³ - x² - x - 1)
            poly1_attempts += 1
            audit_tribonacci = FrobeniusAuditor(
                candidate, [-1, -1, -1, 1]
            ).execute_audit()
            cubic_passed = bool(audit_tribonacci["frobenius_passed"])

            if learned_mode:
                arm_before = learned.arm_events
                learned.observe(p, cubic_passed)
                if learned.arm_events > arm_before:
                    ui.add_log(
                        f"{MAGENTA}[Gap Arm] "
                        f"gap={learned.last_gap:,} "
                        f"fraction={learned.last_fraction * 100:.1f}% "
                        f"skip={learned.last_skip_size:,} "
                        f"cycle={learned.cycle_index}{RESET}"
                    )

            if not cubic_passed:
                continue

            poly1_survivors += 1
            ui.add_log(
                f"[Cubic Survivor] n={candidate:,} p={p:,} q={q:,}"
            )

            # Authoritative $620 verification
            six20 = verify_620(candidate)
            log_line = print_620_report_to_string(candidate, six20, witness=p)
            ui.add_log(log_line)

            if six20["620_candidate"]:
                true_620_hits.append(candidate)
                logger.write(
                    "$620 WINNER",
                    n=candidate, p=p, q=q,
                    reason="VALIDATED",
                )
                if learned_mode:
                    learned.record_tier_hit(p, "$620")
                ui.add_log(
                    f"{GREEN}[!!! $620 WINNER !!!] "
                    f"n={candidate:,} = {p:,} × {q:,}{RESET}"
                )

            now = time.monotonic()
            ui.refresh(
                now - t0, checked_pairs, poly1_survivors,
                sieve_status_str, len(true_620_hits),
                p, ratio_strat, max_p,
                current_q=q, current_candidate=candidate,
                phase="SEARCHING",
                q_tested=q_tested, q_rejected=q_rejected,
                q_screen_rejected=q_screen_rejected,
                residue_rejected=residue_rejected,
                fermat_rejected=fermat_rejected,
                quadratic_attempts=quadratic_attempts,
                quadratic_survivors=quadratic_survivors,
                quadratic_rejected=quadratic_rejected,
                binary_vantage_survivors=binary_vantage_survivors,
                binary_vantage_rejected=binary_vantage_rejected,
                trinary_vantage_survivors=trinary_vantage_survivors,
                trinary_vantage_rejected=trinary_vantage_rejected,
                poly1_attempts=poly1_attempts,
                poly1_rejected=poly1_attempts - poly1_survivors,
                **learned_kwargs(),
            )
            last_display = now

    finally:
        if learned_mode:
            learned.checkpoint()
        logger.close()

    elapsed = time.monotonic() - t0

    ui.add_log(f"[Complete] p explored through {current_p:,}")
    ui.add_log(f"[Complete] Elapsed {elapsed:,.3f} s")
    ui.add_log(f"[Complete] Validated winners = {len(true_620_hits):,}")

    ui.refresh(
        elapsed, checked_pairs, poly1_survivors,
        sieve_status_str, len(true_620_hits),
        current_p, ratio_strat, max_p,
        current_q=current_q, current_candidate=current_candidate,
        phase="COMPLETE",
        q_tested=q_tested, q_rejected=q_rejected,
        q_screen_rejected=q_screen_rejected,
        residue_rejected=residue_rejected,
        fermat_rejected=fermat_rejected,
        quadratic_attempts=quadratic_attempts,
        quadratic_survivors=quadratic_survivors,
        quadratic_rejected=quadratic_rejected,
        binary_vantage_survivors=binary_vantage_survivors,
        binary_vantage_rejected=binary_vantage_rejected,
        trinary_vantage_survivors=trinary_vantage_survivors,
        trinary_vantage_rejected=trinary_vantage_rejected,
        poly1_attempts=poly1_attempts,
        poly1_rejected=poly1_attempts - poly1_survivors,
        **learned_kwargs(),
    )

    print()
    print(f"{CYAN}SHORTCUT SEARCH COMPLETE{RESET}")
    print(f"Mode                     : "
          f"{'HEURISTIC LEARNED (v7, gap-spiral)' if learned_mode else 'EXHAUSTIVE'}")
    print(f"Strategy                 : {ratio_strat}")
    print(f"Limit                    : {limit_upper:,}")
    print(f"Maximum p                : {max_p:,}")
    print(f"Last p                   : {current_p:,}")
    print(f"Candidate pairs           : {checked_pairs:,}")
    print(f"q modular rejects         : {q_screen_rejected:,}")
    print(f"q primality tests        : {q_tested:,}")
    print(f"q rejected               : {q_rejected:,}")
    print(f"Mod-5 rejected           : {residue_rejected:,}")
    print(f"Fermat rejected           : {fermat_rejected:,}")
    print(f"Quadratic closure attempts : {quadratic_attempts:,}")
    print(f"Quadratic closure survivors: {quadratic_survivors:,}")
    print(f"Quadratic closure rejected : {quadratic_rejected:,}")
    print(f"Binary vantage survivors   : {binary_vantage_survivors:,}")
    print(f"Binary vantage rejected    : {binary_vantage_rejected:,}")
    print(f"Trinary vantage survivors   : {trinary_vantage_survivors:,}")
    print(f"Trinary vantage rejected    : {trinary_vantage_rejected:,}")
    print(f"Cubic attempts           : {poly1_attempts:,}")
    print(f"Cubic survivors          : {poly1_survivors:,}")
    print(f"Validated $620 winners   : {len(true_620_hits):,}")
    print(f"Learning log             : {logger.path}")

    if learned_mode:
        ls = learned.summary()
        print(f"Learned arm events       : {ls['arm_events']:,}")
        print(f"Learned candidates skipped: {ls['total_skipped']:,}")
        print(f"Learned spiral cycle      : {ls['cycle_index']}")
        print(f"Learned current fraction  : {ls['current_fraction'] * 100.0:.1f}%")
        print(f"Learned last gap / skip   : {ls['last_gap']:,} / {ls['last_skip_size']:,}")
        print(f"Learned observations     : {ls['total_observations']:,}")
        print(f"Learned useful           : {ls['total_useful']:,}")
        print(f"Learned cubic yield      : {ls['global_yield'] * 100.0:,.3f}%")
        print(f"Tier hits                : {ls['tier_hits'] if ls['tier_hits'] else '-'}")

    if true_620_hits:
        print()
        print(f"{GREEN}WINNERS{RESET}")
        for n in true_620_hits:
            print(f"  {n:,}")
    else:
        print()
        print("No validated $620 candidates found.")

    return true_620_hits


# =============================================================================
# LINEAR SEARCH
# =============================================================================

def search_620(
    start: int, limit: int, cubic_only: bool = False
) -> List[int]:

    start = max(5, start)
    if start % 2 == 0:
        start += 1

    checked           = 0
    base2_survivors   = 0
    fibonacci_survivors = 0
    cubic_survivors   = 0
    winners: List[int] = []

    t0 = time.monotonic()
    ui = TerminalDashboard(
        "LINEAR COEFFICIENT SEARCH ENGINE",
        log_window_size=LINEAR_LOG_LINES,
    )
    last_display = 0.0

    for n in range(start, limit, 2):
        if n % 3 == 0:
            continue
        checked += 1

        if cubic_only:
            if is_prime_fast(n):
                continue
            audit = FrobeniusAuditor(n, [-1, -1, -1, 1]).execute_audit()
            if not audit["frobenius_passed"]:
                continue
            cubic_survivors += 1
            six20 = verify_620(n)
            if six20["residue_ok"] and six20["base2_ok"]:
                base2_survivors += 1
            if six20["620_candidate"]:
                winners.append(n)
                ui.add_log(f"[Cubic $620 Hit] Candidate: {n:,}")
        else:
            if n % 5 not in (2, 3):
                continue
            if pow(2, n - 1, n) != 1:
                continue
            base2_survivors += 1
            if fibonacci_pair_mod_iterative(n + 1, n)[0] != 0:
                continue
            fibonacci_survivors += 1
            if is_prime_fast(n):
                continue
            winners.append(n)
            six20 = verify_620(n)
            audit = FrobeniusAuditor(n, [-1, -1, -1, 1]).execute_audit()
            if audit["frobenius_passed"]:
                cubic_survivors += 1
            ui.add_log(f"[$620 Hit] Candidate Found: {n:,}")

        now = time.monotonic()
        if checked % 1000 == 0 or now - last_display >= DISPLAY_INTERVAL:
            ui.refresh(
                now - t0, checked, cubic_survivors,
                "LINEAR", len(winners), n, "LINEAR",
                max(start, limit),
                current_candidate=n,
                phase="CUBIC SEARCH" if cubic_only else "620 SEARCH",
            )
            last_display = now

    elapsed = time.monotonic() - t0
    ui.add_log(f"[Complete] Elapsed {elapsed:,.3f} s")
    ui.refresh(
        elapsed, checked, cubic_survivors,
        "LINEAR", len(winners), limit, "LINEAR",
        max(start, limit),
        current_candidate=limit,
        phase="COMPLETE",
    )
    return winners


# =============================================================================
# COMMAND LINE
# =============================================================================

def unified_main() -> None:
    args = sys.argv[1:]

    if not args:
        inspect_candidates([2487941])
        return

    # --learned-shortcut-search LIMIT [RATIO] [--resume-from P]
    if args[0] == "--learned-shortcut-search":
        _run_shortcut(args[1:], learned_mode=True)
        return

    # --shortcut-search LIMIT [RATIO] [--resume-from P]
    if args[0] == "--shortcut-search":
        _run_shortcut(args[1:], learned_mode=False)
        return

    # --search START LIMIT
    if args[0] == "--search":
        if len(args) != 3:
            print("Usage: python script.py --search START LIMIT")
            sys.exit(2)
        try:
            start, limit = int(args[1]), int(args[2])
        except ValueError:
            print("START and LIMIT must be integers.", file=sys.stderr)
            sys.exit(2)
        search_620(start, limit, cubic_only=False)
        return

    # --cubic-search START LIMIT
    if args[0] == "--cubic-search":
        if len(args) != 3:
            print("Usage: python script.py --cubic-search START LIMIT")
            sys.exit(2)
        try:
            start, limit = int(args[1]), int(args[2])
        except ValueError:
            print("START and LIMIT must be integers.", file=sys.stderr)
            sys.exit(2)
        search_620(start, limit, cubic_only=True)
        return

    # Explicit candidate inspection
    try:
        values = [int(x) for x in args]
    except ValueError as exc:
        print(f"Invalid integer argument: {exc}", file=sys.stderr)
        sys.exit(2)
    inspect_candidates(values)


def _run_shortcut(args: List[str], learned_mode: bool) -> None:
    """Parse LIMIT [RATIO] [--resume-from P] and dispatch."""
    mode_flag = (
        "--learned-shortcut-search" if learned_mode else "--shortcut-search"
    )

    # Pull out --resume-from P if present
    resume_from = START
    clean_args  = []
    i = 0
    while i < len(args):
        if args[i] == "--resume-from":
            if i + 1 >= len(args):
                print("--resume-from requires a value.", file=sys.stderr)
                sys.exit(2)
            try:
                resume_from = int(args[i + 1])
            except ValueError:
                print("--resume-from value must be an integer.", file=sys.stderr)
                sys.exit(2)
            i += 2
        else:
            clean_args.append(args[i])
            i += 1

    if len(clean_args) < 1 or len(clean_args) > 2:
        print(
            f"Usage: python script.py {mode_flag} LIMIT_UPPER [RATIO_STRAT] "
            f"[--resume-from P]\n"
            "Ratio Strategies: 3p-2  2p+1  7p-6"
        )
        sys.exit(2)

    try:
        limit_upper = int(clean_args[0])
    except ValueError:
        print("LIMIT_UPPER must be an integer.", file=sys.stderr)
        sys.exit(2)

    strat = clean_args[1] if len(clean_args) == 2 else "3p-2"

    try:
        search_shortcut_space(
            limit_upper,
            strat,
            learned_mode=learned_mode,
            resume_from=resume_from,
        )
    except ValueError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        sys.exit(2)


# =============================================================================
# ENTRY POINT
# =============================================================================

if __name__ == "__main__":
    unified_main()
