target_finder — EETargetFinder (mesh sampling, candidate scoring, FOV coverage marking)

Purpose

Samples surface targets on the inspection mesh, scores candidate camera poses, and marks
per-triangle FOV coverage via Open3D raycasting. The scorer is ANCHOR-only (inert in POSE,
the adopted default), but the coverage marker runs in every mode — so this file is the source
of inspection.area_coverage_frac, not the target-selection lever.

Role in the system

  • Standalone helper (not part of the guidance inheritance chain). Owned by ee_guidance (EEGuidance),
    the leaf of the deep guidance chain com_guidancebase_guidanceee_guidance.
  • Called by ee_guidance: choose_goal (ANCHOR-mode selection) and the coverage marker.
  • Also called by breve_controller: compute_triangle_camera_visibility marks the FOV from the
    actual camera pose every EE_COVERAGE_MARK_STRIDEth step.
  • Geometry is shared from utils/mesh.Mesh (one load, one scene); the finder keeps its own
    KDTreeFlann query trees over that data.
  • Records and enums (CameraPose, SurfaceTarget, Selection, ScoringMode) come from guidance_classes.

Inputs / Outputs

  • In: config (camera_guidance.targeting.*), the robot, a Mesh; per-call an anchor p_c or a CameraPose.
  • Out: Selection (chosen target + pose + score breakdown), visible-target index lists, and the
    area-weighted inspection_coverage_data Package (unique triangles, 1× / 2× covered-area fractions).
  • Side effect: mutates coverage memory seen_triangles (set) and triangle_stats (per-triangle counts).

Key methods

  • choose_goal — top-level scorer entry: candidate poses around the anchor → best SelectionGNC/guidance/target_finder.py:546
  • candidate_camera_poses — rank hemisphere poses by visible-target count — :126
  • query_candidate_targets_from_position — position-only visibility (depth + incidence + line-of-sight) — :159
  • compute_triangle_camera_visibility — pinhole-frustum raycast; the actual coverage marker:201
  • score_target — per-(target, pose) score across all terms — :360
  • combined_score — fold terms into one scalar per the active ScoringMode:463
  • inspection_coverage_data — area-weighted coverage summary (the reported metric) — :266
  • build_surface_targets — seed RNG, sample targets, tag nearest triangle — :95

Footguns

PRODUCT mode makes the weights INERT

ScoringMode.PRODUCT multiplies the raw score terms with no per-term weights — the
scoring.weights config keys do nothing in that mode. The active default is EXPONENTIAL (TASK_7),
which is weight-aware (equals PRODUCT when all weights are 1.0). (GNC/INSIGHTS.md [footgun])

Reproducibility rides on the Open3D global RNG seed

Open3D’s surface sampler takes no seed argument. build_surface_targets seeds the global RNG
(o3d.utility.random.seed(int(scoring.sampling.seed))) so surface sampling → target selection →
TARGETING is reproducible. Anything that re-seeds or reorders that global RNG breaks byte-identity.
(GNC/INSIGHTS.md [config])

Query trees must stay KDTreeFlann for bit-identity

The finder keeps its own KDTreeFlann query trees over the shared Mesh data so query semantics
stay bit-identical with the pinned baselines. A cKDTree swap is a deferred perf change, not a
free substitution. (GNC/INSIGHTS.md [perf])

Scorer is ANCHOR-only; coverage is trajectory-driven

In POSE mode (the adopted default) the pose is scheduled and the scorer is inert — yet coverage
still accrues because compute_triangle_camera_visibility marks the FOV in every mode. Don’t tune
scoring weights / reselect cadence to move coverage; that lever is the orbit trajectory. (GNC/INSIGHTS.md [guidance])

No stable score leaf for no-target / fallback steps

score_target flags (# MAJOR ISSUE) that it does not expose a stable score leaf for
no-target / fallback steps — the score breakdown collapses to the NO_SCORE() NaN sentinel.

Pseudocode (ANCHOR-mode goal selection)

choose_goal(p_c):
    candidates = candidate_camera_poses(p_c)          # hemisphere around the anchor, ranked by visible count
    for pose in candidates:
        for target in query_candidate_targets_from_position(pose.p_e):   # depth + incidence + LOS gate
            s = score_target(target, pose, p_c)        # view·novelty·area·stability·motion·anchor·manip
            best = s if s.total_score > best.total_score else best
    return best                                        # Selection (target + pose + score terms)

# every step (POSE or ANCHOR), strided in the controller:
compute_triangle_camera_visibility(actual_pose)        # raycast frustum → seen_triangles, triangle_stats
inspection_coverage_data()                             # area-weighted 1× / 2× coverage fractions

Equations & references

Key equations mirrored from current_sota — the math source of truth; see there for derivations.

Area coverage (surface-area fraction seen) — §7, eq (7.1):

ANCHOR weighted-product score (inert in POSE; novelty ) — §7, eq (7.2):

References:

  • Coverage / pointing metrics (§7): current_sota > 7 — implemented at GNC/guidance/target_finder.py:201
    onward; the pointing versine 1 − cos θ itself lives in the measurement layer, not here.

ee_guidance · guidance_classes · breve_controller · guidance_rollout · terminology · target_finding_coverage