Two defects behind "arrows drawn while members are still mid-era".
1. THE DRAW. The filtered overlay armed on the FIRST member to finish
pass 3 and leaned on a 60 s rate limit to "collapse the burst",
assuming members finish seconds apart. They do not - on USDJPY one
member was at sample 10496 of pass 2 while another was at 2304,
minutes apart. A member with no era-end snapshot returns false from
SnapshotVoteAt, and the sweep's `if(!hasData) continue;` skips it
BEFORE `den += ModuleWeight()`, so the one finished model's tier
weight became the entire vote and was drawn as a consensus arrow.
An abstention is a member that looked at the bar and said nothing; a
missing snapshot is a member that has not looked. The first must
dilute the vote, the second must suppress the draw. The arm is now a
readiness MASK - one bit per m_ensembleIndex, set at that member's
pass-3 completion, cleared when a sweep arms - and a sweep waits for
every enrolled member. Bounded at 10 minutes so a member that stops
cannot freeze the chart, and the partial draw PRINTS which members
were missing: the be39674 lesson is that a hold must never silence
the thing that reports it.
2. THE VOTE ITSELF, which is the worse half and is not display-only.
Tier weights are not persisted in the .nnw - they exist only as the
output of a completed pass 3 - so before a member's first
RankTiersFromOos() it holds the constructor's stock 25/50/75/100.
Since 4858507 the vote currency is a WIN RATE, so an unranked tier-3
call enters the capability-weighted mean claiming a 100% win rate
beside ranked members contributing ~25. Not a strong opinion: the
wrong unit. One unranked member drags the ensemble over any
threshold, on every fresh deploy and every resume. USDJPY has a
measured ceiling of ~19 and was firing anyway.
LiveVoteContribution() now abstains until self-ranked, which drops
the member from the sum AND the divisor. One function, so live and
the gate move together (2c443ba).
Era 0 will therefore report 0 coverage until each member completes one
era. The ensemble line says so explicitly rather than leaving it to look
like the USDJPY unreachable-threshold case - the two are identical in
the coverage number and completely different problems.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>