06_Other/ballot_style_lab — random-but-human electorates (the style lab)¶
What happens when you fill a whole election with the ballot styles real people actually use — bullet votes, partisan slates, harsh graders who top out at 2, gentle souls who never score below 3, cliff voters who jump straight from 0 to "one of mine"? This folder is a seeded generator plus ten frozen elections it found — six single-winner STAR, plus a multi-winner wing (Bloc STAR and STAR-PR, up to 7 candidates and 4 seats): random enough to surprise, human enough to mean something, deterministic enough to be test cases.
→ The style taxonomy these voters follow: Filling Out the 5-Star Ballot — Voting Styles · the methodology this generator obeys: Simulate utilities, not ballots · one voter's opinion across ballot formats: Alternate ballot styles
The idea — opinion first, expression second¶
Pure-uniform random ballots correspond to no electorate on Earth, and the methodology page explains why they rig method comparisons. So this generator injects randomness in two human-shaped layers:
- Opinion (layer 1): each voter's underlying 0–1 utilities are sampled from a faction model — slanted camps with a shared lean, personal noise on top, plus a few genuinely noisy voters.
- Expression (layer 2): those utilities are then rendered through the voter's ballot style — the legal styles from the style gallery (bullet, backups, slate, ranked-style, nuanced, anyone-but, protest) plus deliberately compressed scale habits (0–2 harsh, 3–5 gentle, 0-or-3–5 cliff, 3–4 sliver, flat lines, pure noise).
Same opinion, different rendering — the same-opinion line-up idea, scaled up to a whole electorate. Per the methodology page, this sits in the honest "stress-test the tabulator" job (not a method-welfare comparison): the ballots exist to exercise the count, and the human shaping makes the exercises look like elections instead of dice.
Full disclosure — the seeds are hunted. The generator tabulates hundreds of seeds with the real LH engine and keeps the interesting draws: runoff reversals, Condorcet cycles, tie rungs, photo finishes, Equal-Support blowouts. A typical random seed is boring; these six are champions selected across 250 seeds each, then frozen. That selection bias is the point of a test fixture — and it's disclosed here and inside every file.
The frozen elections — single-winner wing¶
Every file records its scenario + seed and regenerates byte-identically (--emit <scenario>); expected_winners is embedded, and the single-winner files run in the repo's positive test suite (the multi-winner files are engine-verified on emission — the suite is single-winner-scoped by design). All cases use 24–47 individual ballots and fresh casts per the naming rules. Files 1–6 are single-winner STAR; the multi-winner wing below covers Bloc STAR and Allocated Score / STAR-PR at three field sizes (5c/3 seats, 6c/2 seats, 7c/4 seats).
| # | Case | The electorate | What the frozen seed produced | Source |
|---|---|---|---|---|
| 1 | The Graders' Divide | 15 harsh graders (0–2) vs 16 gentle souls (3–5) — two grading cultures, zero overlap | Totals bunch at 80–80–78; the top two tie dead even and Clara wins the runoff 13–11. No Condorcet winner; Plurality & RCV-IRV say Bruno, Approval-thinking says Abby — four counting philosophies, three different "winners" from 31 ballots | yaml · report |
| 2 | Cliff City | 40 food-truck voters, all cliff/slate ballots — the 1–2 middle of the scale is a ghost town | Scoring 100–99; Churro beats Bao 15–14 in the runoff (11 Equal Support) and is the Condorcet winner; Plurality and RCV-IRV both pick Bao — the engine flags the center-squeeze signature | yaml · report |
| 3 | Bullet Storm | Three bullet-voting brigades (many leave blanks, as real bulleters do) + a thoughtful few who spread scores | The runoff ties 13–13 (7 Equal Support — a whole brigade went silent) and is broken by STAR's official next tiebreaker: higher scoring total → Carla. Plurality & RCV-IRV say Astrid | yaml · report |
| 4 | Noise Soup | 47 messy ballots: weak leans, cross-winds, flat-liners, pure noise, a race abstention ~, a spoiled ?, stray blanks |
A genuine Condorcet cycle out of noise; scoring leader Beth loses the runoff to Caleb 18–16 (13 Equal Support); Plurality says Aaron, RCV-IRV says Beth. Markers all tabulate as 0 and are reported honestly | yaml · report |
| 5 | Does the squeeze survive noise? | The center-squeeze profile rebuilt from noisy utilities + mixed styles (nuanced, ranked-style, backups, harsh, slate, gentle) | Yes. Consensus-Ben trails the scoring round, wins the runoff 19–18, and is the Condorcet winner; RCV-IRV eliminates him and elects Cora — the tidy classroom demos aren't cherry-picked, the squeeze survives real-world mess | yaml · report |
| 6 | Narrow Bands | 24 paint-swatch voters and nobody uses the whole ballot: 0–2 camp, 3–5 camp, cliff & sliver voters, flat-liners, one protest | Compressed totals produce a three-way scoring tie 61–61–61; the official head-to-head tiebreaker picks the finalists, the runoff lands 9–8, and there's no Condorcet winner. Plurality, RCV-IRV and Approval-thinking all say Azure; STAR says Beige, Ranked Robin says Coral | yaml · report |
Voter counts here (24–47) deliberately sit above the repo's keep-it-small default (choosing voter counts): statistical style patterns need a crowd, and 20–50 is still small enough to eyeball every row.
The frozen elections — multi-winner wing¶
Bloc STAR (single-winner STAR run once per seat — a majority can sweep) and Allocated Score / STAR-PR (each Hare quota fills one seat, spent ballots reweighted), at deliberately different field sizes. The headline is the twin pair: files 07a/07b tabulate the identical 36 ballots under both philosophies and seat different councils.
| # | Case | Field | What the frozen seed produced | Source |
|---|---|---|---|---|
| 7a | Herb Garden Council — Bloc | 5 cand · 3 seats · 36 ballots | The kitchen bloc sweeps all three seats — and the sweep hangs by a thread: the final seat's runoff ties, and the official tiebreaker settles it on total scores Dill 83 vs Anise 82. One star of score decides seat #3 | yaml · report |
| 7b | Herb Garden Council — STAR-PR | same 5 cand · 3 seats · the identical 36 ballots | The tea caucus's quota seats Anise; kitchen keeps two (final reweighted round Chive 35.58 vs Dill 33.38). Same rows, different philosophy, different council — the majoritarian-vs-proportional lesson as a live pair | yaml · report |
| 8 | Quota Circus | 6 cand · 2 seats · 29 ballots | The provocation: under proportional STAR-PR the north side sweeps both seats (final round Bree 39.25 vs Cato 36.94) — because the south's gentle 3–5 ballots donate floor-3s to their rivals. Grade inflation is unilateral disarmament, quota edition | yaml · report |
| 9 | Replant the Park | 7 cand · 4 seats · 44 ballots | Four consecutive STAR counts (margins 4, 7, 4, 0): the shade trio sweeps seats 1–3, then the evergreen duo tie for the last chair — Cedar beats Fir on the score rung 110–101. Deepest Bloc exercise in the repo | yaml · report |
The multi-winner files need no display options — the engine switches the finalists matrix off for multi-winner races automatically (a "Top 2 Finalist" matrix is a single-winner concept). The hand-built companion set for the sweep-vs-share lesson is 03_STAR_PR/_main/02a–02c; the style lab adds realistic style mixing, individual rows, and the tie drama.
A taste — the Graders' Divide on screen¶
Two camps that never touch each other's half of the scale. Look at the Score Distribution: Abby's column is nothing but 2s and 3s (all 31 voters!), Bruno's is nothing but 5s and 0s — and the totals land 80–80–78:
[Divergence from STAR]
STAR = Clara
Choose-One (Plurality) = Bruno (differs from STAR)
RCV-IRV = Bruno (differs from STAR)
Approval = Abby (differs from STAR)
Note: 9 of 31 ballots (29%) had equal non-zero scores, so their ranks were
decided by candidate priority order. The RCV-IRV result may be an
artifact of score-to-rank tie-breaking rather than a deep
difference.
Note: Ranked Robin (RCV-RR) agrees with STAR, so RCV-IRV is the lone
outlier — the classic center-squeeze signature.
Full round-by-round reports (generated for review):
RCV-IRV rounds: cases_tabulated/01_c3_b31_graders-divide_RCV-IRV_tabulated.txt
--- STAR Voting Method (single winner) ---
[STAR Voting]
Tabulating 31 ballots.
Count × Abby,Bruno,Clara
12 × 2, 0, 1
8 × 3, 5, 4
5 × 3, 5, 5
3 × 3, 5, 3
2 × 2, 0, 0
1 × 2, 0, 2
[STAR Voting: Scoring Round]
The two highest-scoring candidates advance to the next round.
Bruno -- 80 -- First place
Clara -- 80 -- Second place
Abby -- 78
Bruno and Clara advance.
[STAR Voting: Automatic Runoff Round]
The candidate preferred in the most head-to-head matchups wins.
Clara -- 13 -- First place
Bruno -- 11
Equal Support -- 7
Clara wins.
Runoff math:
31 ballots cast
− 7 Equal Support (no preference between the two finalists)
──
24 voters with a preference (majority = 13)
Clara 13 (54%) · Bruno 11 (46%)
[STAR Voting: Winner — STAR Voting Method (single winner)]
Clara
The Scoring Round can't tell a harsh 2 from a gentle enthusiasm — but the Automatic Runoff only asks which finalist each ballot scored higher, and on that question the grading cultures cancel out. Full detail (matrix, divergence block, the Condorcet wobble): the _tabulated report.
The style menu¶
Renderings implemented by the generator (utilities → 0–5 scores). Gallery styles carry the style-gallery names; band styles are the lab's additions.
| Style | Renders as | From |
|---|---|---|
nuanced |
honest min-max onto the full 0–5, ties kept | gallery |
bullet |
favorite 5, everyone else 0/blank | gallery (Traditional) |
strong_backup / weak_backup |
5 plus a 4 (or a grudging 1) for the runner-up | gallery |
slate |
equal 5s for the in-group, 0 outside | gallery (Partisan slate) |
ranked_style |
each score used once, 5 downward, like a ranking | gallery |
anyone_but |
5 for everyone except the villain (0) | gallery |
protest |
all zeros plus a lone least-bad 1 | gallery |
harsh |
everything squeezed into 0–2 | band |
gentle |
everything squeezed into 3–5, zeros never | band |
cliff35 / cliff34 |
0 or 3–5 (or 3–4) — nothing in between | band |
flat |
the same score for everyone (legal no-preference ballot) | band |
chaos |
uniform noise — the fuzzing voter the methodology page blesses | band |
Realism garnishes: bullet/backup/slate voters flip a coin between writing 0s and leaving real-world blanks (-); Noise Soup adds a race abstention row (~), a spoiled row (?), and stray blank cells — all tabulate as 0 and are reported honestly.
Running the lab¶
python generate_ballot_styles.py --list # scenario menu + frozen seeds
python generate_ballot_styles.py --emit all # regenerate all ten files byte-identically
python generate_ballot_styles.py --hunt cliff_city --seeds 250 # go gem-hunting yourself
The hunter tabulates every seed with the real LH engine and scores it for: runoff reversals, no-Condorcet/cycles, RCV-IRV divergence (and the engine's center-squeeze note), Equal-Support share, runoff margins and tie rungs, finalist-line photo finishes, and lot-decided anything. Multi-winner scenarios add slate-aware signals — sweeps, shutouts, dark-horse seats, per-seat margins, final-seat ties, reweighted seat-round gaps — and a twin-pair mode that tabulates each seed under two methods (Bloc vs PR) and scores the seat difference. Champions must also satisfy the scenario's target predicate and resolve deterministically (a lot-decided result can't be a frozen test case; the hunter rejects those seeds). To add a scenario: give it factions, a style mix, a fresh cast (naming rules), optional slates/variants, hunt, freeze the seed, --emit.
Stdlib-only; runs with the repo .venv (or any Python 3) and calls starvote_larry_hastings.py for every tabulation — the engine is the oracle, the generator never reimplements the count.
Related¶
- Filling Out the 5-Star Ballot — Voting Styles — the human taxonomy these voters follow
- Simulate utilities, not ballots — why layer 1 exists at all
- Election simulation models — the menu of utility models beyond this lab's faction-Gaussian
06_Other/simulations/— the brute-force method-comparison sims (utility-first, like this lab)- Divergence review index — where these cases' method disagreements are ledgered