Skip to content

Flat scores → abstention: why the fix is contested

Status: analysis only. No change is proposed for merge here. These pages exist to answer one question honestly — why do the Equal Vote maintainers hesitate to fix this, and are they right to? — and to lay out the options so the decision can be made on evidence rather than on who repeats themselves loudest.

Short answer: they are partly right. The tabulation rule is wrong, but two of the three reasons people give for leaving it alone are real, and one of them is a genuine regression risk that would land the day the fix ships. There is a sequencing that gets the fix in without any of it. That's the recommendation in 04-options.md.

Two findings carry more weight than the rest:

  • Equal Vote's own prior engine disagrees with the current rule. Equal-Vote/starpy has no abstention handling at all — it sums every ballot row and counts only strict preferences. #884 diverged not from an outside implementation but from Equal Vote's own lineage, and from its own published hand-count protocol. See 06-what-the-rules-say.md.
  • "The winner never changes" does not make this cosmetic. An 80-member organisation with a 50%-turnout quorum, 44 members voting, 30 of them scoring both nominees 5,5: BetterVoting reports 14 voters and the quorum fails. Same winner, different governance outcome.

The bug in one paragraph

BetterVoting classifies a STAR ballot as an abstention if every mark is equal after null is coerced to 0. So 5,5,5, 3,3,3, 0,0,0 and 0,null,null are all treated the same way: the ballot is removed from the tally entirely — it adds nothing to the score totals, nothing to the pairwise matrix, and does not count toward nTallyVotes. A voter who gave every candidate five stars is told they "Abstained — No preference." An election in which every ballot is flat reports "Still waiting for results. No votes have been cast" and, in some configurations, still names a winner. The reference implementation (Larry Hastings' starvote) counts any explicitly scored ballot as cast and abstains only a truly blank ballot; the flat ballot lands in the runoff's Equal Support bucket, where it belongs.

Source of the rule: Util.ts:96-103, enabled for STAR at Star.ts:13 and for STAR-PR at AllocatedScore.ts:26. The policy was decided deliberately in #884 — this is not an accident anyone forgot to fix.

Upstream reference cases: Flat scores, ties & tie-breaking in star-voting-library, cases 07 and 08.

The three hesitations, graded

# The hesitation Verdict
1 "The runoff chart looks wrong after the fix" Real, and unavoidable. The Equal Support bar grows by exactly the number of flat ballots and can become the tallest bar on the chart, above the winner. See 03-reporting-anomalies.md.
2 "It'll break something else" Real, but narrower than feared. It widens an existing division-by-zero (already live — #1035), and it changes the quota in proportional STAR, which flips seats. Single-winner and Bloc STAR survived a 55,000-election falsification attempt. See 02-blast-radius.md.
3 "Ties and random tie-break order will shift" False. The random tie-break seed is derived from the raw ballot count, not the tally count, and is explicitly documented as such. Nothing about the shuffle moves. See 02-blast-radius.md §6.

What the pages contain

Page What's in it
01-the-rule.md Exactly what the code does today, line by line, including the frontend/backend divergence nobody has written down
02-blast-radius.md Every surface that changes, cited to file:line, split into cosmetic vs outcome-changing
03-reporting-anomalies.md The "reporting looks strange" claim, with worked before/after numbers. This is the heart of it
04-options.md Six options from "do nothing" to "full data-model fix", with cost, blast radius, and a recommended sequence
05-issue-map.md Which half of the rule causes each ticket — the join onto the roster in star-voting-library. Also: every evidence link on #1407 is a dead 404
06-what-the-rules-say.md The policy half: STAR's published canon, election-administration standards, the quorum hazard, and what other engines do

The one-line recommendation

Split the change in two, and fix #1035 first.

The change everyone argues about is actually two independent edits:

  • (a) stop passing markAllEqualAsAbstention = true for STAR — makes 5,5,5 and 3,3,3 cast votes;
  • (b) stop coercing null → 0 — makes explicit 0,0,0 distinct from blank.

(a) is four characters, fixes most of the reported cases, makes STAR consistent with every other method on the platform, and structurally fixes the write-in bug below. (b) changes Approval, Plurality, IRV and STV semantics — it is implementable (the data model preserves nulls on every path, contrary to an earlier draft here), but it is a much larger conversation. They have been argued as one change, and that is why the discussion has not moved in a year.

Before either lands, #1035 (the NaN% in the runoff pie) must be fixed. It is already live today — the abstention rule keeps its trigger set narrow, and the fix widens it.

The finding that reverses the burden of proof

Checking what the change would break turned up four live defects that are nobody's ticket. The first one matters most:

An approved write-in silently deletes ordinary ballots — and flips winners. A ballot's marks only carries keys that ballot listed, and no voter carries a key for a write-in someone else added. The abstention test runs on those raw keys, before the zero-fill. So once a write-in is approved, every ballot scoring all official candidates equally and non-zero is deleted as an "abstention" — though zero-filled it strictly prefers every official over the write-in. Executed: the write-in wins under today's rule, loses under the fix. The winner flips in 10,632 of 40,000 fuzzed elections.

Edit (a) structurally fixes this class, because "all marks zero" is invariant under zero-filling missing keys while "all marks equal" is not.

That reframes the whole discussion: the question is not whether it is safe to change the rule, but how long it is safe to keep it.