Multi-Template Consensus Matching

A button often renders in several states — default / hover / pressed / disabled — yet it is a single logical target. ab_locator A/B-tests which single strategy wins and match_template(scales=...) sweeps one template across scales — neither fuses multiple reference crops into one vote. match_ensemble matches each reference, clusters the hit centres and accepts a target only when at least min_votes references agree within agree_px — sharply cutting false positives on themed / animated UI.

The voting core (vote_centers) is pure-stdlib and reuses grounding_consensus for the clustering, so it is unit-testable with no image; only match_ensemble itself calls visual_match.match_template (testable on a synthetic injected haystack). Imports no PySide6.

Headless API

from je_auto_control import match_ensemble, vote_centers

# three reference crops of the same button (default / hover / pressed)
result = match_ensemble(["btn_default.png", "btn_hover.png", "btn_pressed.png"],
                        min_score=0.8, agree_px=10, min_votes=2)
if result:
    click(*result["point"])     # {point, votes, n_candidates, spread}

# or vote hit centres you already have
vote_centers([[100, 100], [102, 98], [400, 400]], agree_px=10, min_votes=2)

match_ensemble returns {point, votes, n_candidates, spread} for the consensus location, or None if fewer than min_votes references agree. vote_centers is the pure voting step over candidate centres you supply.

Executor commands

AC_match_ensemble (templates / min_score / agree_px / min_votes / region{found, result}) and AC_vote_centers (centers / agree_px / min_votes{found, result}). They are exposed as the MCP tools ac_match_ensemble / ac_vote_centers (read-only) and as the Script Builder commands Match Ensemble (vote references) / Vote Centers (consensus) under Image.