Settle Detection Over a Churn Series

smart_waits.wait_until_screen_stable and actionability’s stability check bake the settle logic inside a time.sleep polling loop over live pixel frames — you cannot feed them a recorded series of a11y-element counts or screen-diff metrics, and you cannot unit-test the decision independently of capture. settle_detector extracts that decision: it takes a stream of churn values (how much changed each sample — a pixel delta, an element-count delta, a digest-changed 0/1, anything) and reports when the churn has stayed at or below max_churn for quiet_samples in a row. A spike resets the quiet run, so “settled then changed again” is handled.

Pure-stdlib; deterministic and unit-testable on an injected series with no capture and no clock. Imports no PySide6.

Headless API

from je_auto_control import settle_point, is_settled, SettleTracker

churns = [5, 4, 0.5, 0.3, 0.2]          # per-frame change metric
settle_point(churns, quiet_samples=3, max_churn=1.0)   # -> 4
is_settled(churns, quiet_samples=3, max_churn=1.0)     # -> True

# incremental, for a live loop (you supply the churn each tick)
tracker = SettleTracker(quiet_samples=3, max_churn=1.0)
state = tracker.update(current_churn)
if state.settled:
    observe_now()

settle_point returns the index at which the series first settles (or None); is_settled is the boolean. SettleTracker is the incremental form: update(churn) returns a SettleState (settled / quiet_run / churn); reset clears the run (e.g. right after acting again).

Executor command

AC_settle_point (churns / quiet_samples / max_churn{settled, index}) is exposed as the MCP tool ac_settle_point (read-only) and as the Script Builder command Settle Point (churn series) under Flow.