GNC/guidance/target_finder.py — FOV coverage & ANCHOR scoring

EETargetFinder: marks per-triangle FOV coverage and scores candidate camera poses. Coverage is trajectory-driven — the scorer is inert in POSE (the adopted default).

Per-triangle hit count

At each pose, every triangle struck within the working depth band is counted:

htrihtri+1h_{\text{tri}} \leftarrow h_{\text{tri}} + 1

hh is the shared ground truth for both coverage and novelty — one counter, two uses.

Area coverage — eq 7.1

Let S\mathcal{S} be the set of triangles seen at least once. Area coverage is:

coverage=iSAijAj\text{coverage} = \frac{\displaystyle\sum_{i \in \mathcal{S}} A_i}{\displaystyle\sum_j A_j}

AiA_i = triangle area. Independent of candidate sampling — driven by orbit traversal.

Novelty score

Per-triangle novelty from the hit count hh:

σn=11+h\sigma_n = \frac{1}{1 + h}

Fresh triangle (h=0h=0): σn=1\sigma_n = 1. Repeatedly seen (hh \to \infty): σn0\sigma_n \to 0.

ANCHOR weighted-product score — eq 7.2

Candidate (target, pose) pairs are ranked by:

Ω=iσiwi\Omega = \prod_i \sigma_i^{w_i}

σi[0,1]\sigma_i \in [0,1]: view quality, novelty σn\sigma_n, area, stability, motion, anchor, manipulability. Weights wiw_i active only in EXPONENTIAL mode — inert in POSE.

View-quality sub-scores

Three geometry terms folded into σview\sigma_{\text{view}}:

q1=dist ramp(d,Dmin,Dmax)q2=cosθincq3=1dpixdpix,maxq_1 = \text{dist ramp}(d, D_{\min}, D_{\max}) \qquad q_2 = \cos\theta_{\text{inc}} \qquad q_3 = 1 - \frac{d_{\text{pix}}}{d_{\text{pix,max}}}

σview=wdq1+wθq2+wξq3WKong\sigma_{\text{view}} = \frac{w_d q_1 + w_\theta q_2 + w_\xi q_3}{W_{\text{Kong}}}

Coverage: orbit traversal, not selection

In POSE mode the scorer never runs — yet:

coveragefull helix1.0\text{coverage} \xrightarrow{\text{full helix}} 1.0

The lever is the orbit path (pole-to-pole helix), not scoring weights or reselect cadence. Achieved: coverage =0.9990= 0.9990 at the adopted operating point.