Which interventions bring a polarized society back together, and does contact backfire?
research/civ-levers
Which interventions bring a polarized society back together? In an opinion-dynamics model with backfire swept from none to strong, bridging ties across camps never merges them and at a rejection threshold of 0.7 rewiring 10% of ties drives everyone to the extremes, while a ladder of committed voices within reach merges them in 24 of 25 settings (moderate broadcast 19, nudge 14, tolerance 8, bridging 2); interactive simulator included (tolerance and backfire are swept, not measured).
35 tests
What it found
1,000 simulated people in two camps (opinions near 0.2 and 0.8, 95% of ties within camp) follow bounded confidence (Deffuant et al. 2000) with optional repulsion from distant views (Jager & Amblard 2005); the model reproduces Deffuant’s floor(1/(2ε)) cluster count and the Hegselmann–Krause consensus threshold. Six levers are swept over dose, tolerance ε 0.2–0.4 and rejection threshold τ (none, 0.9–0.6), 10 societies each. Rewiring ties across camps never merges them below ε = 0.4 and is the only lever that backfires: at τ = 0.7, rewiring 10% of ties sends everyone to the extremes (10% with no lever). Over 25 settings at top dose, a ladder of committed voices merges the camps in 24, a moderate broadcast or committed moderates in 19, a one-off nudge in 14, raising tolerance in 8 and bridging in 2 (more people at the extremes in 10). A ladder must be spaced for the least tolerant: built for ε = 0.3 when the real one is 0.2, it merges none of 10. Tolerance, rejection threshold and the network are not measured for any real population and are swept, not sourced; opinions are one-dimensional and contact here changes positions only, not prejudice. A follow-up seeds a threshold (complex) contagion on Watts–Strogatz graphs: on clustered networks a patch of 2–3 neighbours starts a cascade that spread-out or random seeds need 12–20 to match, and with threshold 3 nothing past 40 seeds works once ties are rewired (stylised: threshold, degree and graph family chosen, not measured). A third follow-up runs Axelrod’s culture model with a common broadcast: at F = 5, q = 10 on a 20×20 lattice a broadcast heard 30% of the time fragments a society that is one culture without it (largest culture 99% → 26% of sites), because sites sharing no feature with the media never interact with it; the F = 10 order–disorder drop appears at q ≈ 30–50 on small lattices, not reproducing Castellano et al.’s qc ≈ 300 (lattices to L = 150, not run here). A fifth follow-up builds a toy structural-demographic civilization (Turchin 2003): across 864 parameter cells (none measured) it never produces secular cycles, with or without a doubled tax, an elite cap or a welfare transfer, so it cannot yet say which levers damp them. Includes an interactive browser simulator.
A figure from its own output
civ-levers/experiments/bridging_backfire.svg, written by the project's own experiment code.
Research log
Ongoing: each run adds a batch and an entry, newest first. Source: LOG.md.
2026-10-31 · Lagged elite recruitment and lagged stress (milestone 7, second run)
Plan (written before the run): add two delays to history.py, lag (elite recruitment responds to income smoothed over lag years, a generational delay) and plag (political stress smoothed over plag years), both 0 by default so the first run is reproduced exactly. H1: some lag setting gives cycles with period 150 to 400 years (Turchin and Nefedov 2009) at a population swing (std/mean) of at least 0.10. Refuted if no cell reaches both. 1350 cells (rho, m, k, g, lag, plag).
Outcome (experiments/history/lag_results.txt): H1 half held. Period: with lag 200 y, 20 to 28 of 54 cells per plag setting oscillate and 17 to 23 are in the 150 to 400 y band; lag 100 gives 4 to 8; lag 50 or less none. Amplitude: refuted. The largest swing in any cell is 0.089 (lag 200, plag 200) and most in-band cells swing under 0.03, with unrest share 0. These are small damped-then-sustained wiggles of a stable model, not secular cycles with collapse.
Reading: delay alone sets the period (about 1.5 to 2 times the lag) but not a large amplitude; the missing piece is probably the collapse reset (state failure removing elites and resetting stress) and wage-driven elite income. Next: add that reset, keeping 150 to 400 y and swing at least 0.10 as the acceptance check.
Caveats: all parameters swept, none measured; period by autocorrelation of N only; the 1.5 to 2 times lag reading is from the in-band rows, not tested separately. Two new tests.
2026-10-31 · Pooled sanctioning against peer punishment (milestone 5, second run)
Plan (written before the run): add pooled=True to commons.py: one fine per member below the group mean (same schedule: shortfall/4 tokens, cap 10, 3 removed per token), cost split equally across the group. H1: at n = 40 and react 1, pooled payoff beats no punishment (21.0) and peer punishment (2.9). Refuted if pooled payoff at n = 40 is at or below 21.0. H2: pooled holds contributions as high as peer punishment. Refuted if pooled round-10 contribution is more than 2 below peer at the same react.
Outcome (experiments/commons/pooled_results.txt, 30 seeds, 240 people, 10 rounds): H1 half held: pooled payoff beats peer at every n and react (n = 40, react 1: 21.2 against 2.9), but beats no punishment (21.0) only slightly at react 1 (21.2) and clearly at react 2 (25.5). H2 refuted: with one fine per violator instead of one per punisher, the same react gives a weaker push: contribution 3.5 at react 1 against 16.1 for peer; it reaches 12.6 only at react 2. Group size barely matters for pooled (3.46 to 3.49 at react 1 from n = 8 to 40), which is the point: cost no longer scales with the number of punishers.
Reading: the earlier "punishment cost grows with group size" result is mostly the all-to-all rule; an institution fixes the cost but needs a stronger behavioural response to hold cooperation. react stays unmeasured, so which institution wins depends on it; fitting it to Fehr and Gächter's rounds is still next. Two new tests in tests/test_commons.py.
2026-10-30 · A toy structural-demographic civilization (milestone 7, first run)
Plan (written before the run): src/civ_levers/history.py, Turchin-style: fixed land, Cobb-Douglas output (labour share 0.5), peasants' wage w, rent to state and elites, elites grow when income per head beats aspiration (overproduction), state strength relaxes to tau R / (g E), political stress PSI = (E/E*)(s0/w)/S, instability psi = logistic(k (PSI - 1)) killing peasants and elites. H1: for some corner of the unmeasured parameters (rho, m, me, k, tau, g, e0f; 864 cells) the population oscillates with a 150 to 400 year period (Turchin and Nefedov 2009 secular cycles). Refuted if no cell cycles. H2: where cycles exist, a doubled tax, an elite cap or a welfare transfer lowers the share of unrest years. Check first (analytic): with instability off the Malthusian fixed point N = 1 is reached.
Outcome (experiments/history/results.txt): H1 refuted: 0 of 864 cells cycle, with or without any of the three levers; the largest population swing (std/mean) is 0.0023. The model settles either at a peaceful equilibrium or (3 cells, high g and large elite aspiration) in permanent unrest. H2 is moot with no cycles, though the levers do change the minimum population on the way (mean 0.757 none, 0.850 doubled tax, 0.917 welfare 0.1). Analytic check passes (tests/test_history.py).
Reading: this minimal closure is a damped system. Turchin's cycles need delayed, nonlinear feedback the model lacks (elite numbers responding to population with a generational lag, state collapse that resets stress, and the per-capita elite income that rises when peasants' wages fall). So the model has no policy question to answer yet; adding those mechanisms is the next step, with the cycle period (150 to 400 years) as the acceptance check, not a tuned outcome.
Caveats: all seven parameters are swept, none measured; period detection by autocorrelation of N only; this null is about this model, not about Turchin's.
2026-10-30 · Public goods with peer punishment (milestone 5, first run)
Plan (written before the run): groups of n with return 1.6/n per token (0.4 at n = 4, endowment 20, punishment cost 1 for 3 removed, as in Fehr and Gächter 2000); 30% free riders, the rest conditional cooperators giving 0.85 of the others' last mean; 240 people, 10 rounds, 30 seeds. H1: without punishment contributions fall below half of round 1. H2: peer punishment with a behavioural response sustains or raises them. H3: in large mobile groups punishment keeps contributions up but net payoff falls, because enforcement cost grows with group size. Refuted if payoff at n = 40 stays within 10% of the no-punishment payoff.
Outcome (experiments/commons/results.txt): H1 held (7.5 falls to 1.9 by round 10). First finding was a null: with no behavioural response (react 0) punishment changes no contribution and only lowers payoff (21.1 to 19.8). Contributions are driven entirely by the assumed response rule, so react is the unmeasured parameter and is swept: round-10 contribution 5.4, 10.2, 12.2 at react 0.5, 1, 2 (4-person partner groups). H2 held under that assumption. H3 held: at react 1, stranger groups of 8, 16, 40 reach 14.4, 15.3, 16.1 but payoff falls to 18.1, 11.4, 2.9 against 21.0 without punishment, because every punisher fines every lower contributor, so total cost scales with group size.
Caveats: the response rule, type shares and punisher share are assumed, not fitted; the published rounds were not compared numerically (the paper's figures were not fetched in this run), only the qualitative decay and punishment-sustains shape. The all-to-all punishment rule inflates the large-group cost; a rule where one punisher fines at most one target, or a pooled institution, is the next test.
Next: fit react and lam to Fehr and Gächter's published round means (get the table from the paper), then compare a pooled sanctioning institution (Sigmund et al. 2010 style) with peer punishment at n = 40.
2026-10-28 · Plan: Axelrod's culture model with a broadcast (milestone 4, written before the run)
Model: src/civ_levers/culture.py. Axelrod (1997) on an L x L lattice (von Neumann, no wrap), F features, q traits; broadcast = with probability B per step a site meets a fixed media culture by the same rule (global field, Shibanai, Yasuno and Ishiguro 2001). Not measured: F, q, B, all swept.
Checks first: Axelrod's base case (F 5, q 10, 10x10) reported about 3.2 regions; Castellano, Marsili and Vespignani (2000): for F = 10 a sharp drop of the largest region near q_c about 300 (lattices up to L = 150), for F = 2 a continuous one. At L = 20 to 40 q_c will sit lower (finite size), so I check shape (sharp for F >= 3, continuous for F = 2) and that q_c rises with L, not the number.
H1: the order-disorder transition appears: largest region S near 1 at small q, near 0 at large q, steeper for F = 10 than F = 2. Refuted if S is not monotone falling in q.
H2: a weak broadcast homogenises at small q but fragments at large q: at q 20 or 40 (disordered without media) B = 0.01 raises S. Refuted if S at B = 0.01 is no larger than at B = 0 at q = 40.
H3: a strong broadcast (B = 0.3) leaves most sites with the media culture at every q (media-share > 0.5). Refuted otherwise.
Analysis: 20 runs per cell (300 for the base case), intervals are 1.96 standard errors; results are about simulated agents under a stated rule only.
Outcome (experiments/culture/results.txt, 20 runs per cell): H1 held in shape, not in the published number: S falls with q for F = 2 (0.75 at q 2 to 0.05 at q 8, L = 24, gradual), F = 3 (1.00 at q 5, 0.48 at q 10, 0.05 at q 15) and F = 10 (1.00 at q 20, 0.20 at q 50, 0.02 at q 100), sharper for F >= 3. But the F = 10 drop sits at q about 30 to 50 at L = 12 and 24 where Castellano et al. report q_c about 300 (L up to 150); not reproduced, and I could not run L = 50 or 100 in pure Python in the time (the attempt was dropped). Base case (F 5, q 10, 10x10): 4.55 +- 0.36 regions against Axelrod's 3.2: higher, not matched (his sample was small and his region count rule may differ). H2 refuted: at q = 40 S is 0.03 at every B from 0 to 0.3 (293 to 297 regions); at q = 20 B = 0.01 changes nothing (0.47 against 0.49) and B >= 0.03 fragments further (S 0.33, 0.13, 0.06). H3 refuted: media-share is 0.97 at q = 5 / B = 0.1 but 0.50 at q = 10 / B = 0.3 and 0.27 at q = 20. A strong broadcast fragments a society that is one culture without it: q = 10, S 0.99 at B = 0 down to 0.26 at B = 0.3 (regions 2 to 76); q = 5, regions 1 to 29. Mechanism visible in the rule: a site with zero overlap with the media never interacts with it, so sites far from the media freeze as isolated cultures (media-share 0.13 at q = 40 is those that started on it by chance). Caveat: one media culture, global field only, no noise (Klemm et al. 2003 show noise changes the picture); F, q, B and L are swept, not measured.
2026-10-28 · Plan: where to seed a complex contagion (milestone 3, written before the run)
Model: src/civ_levers/cascades.py. Progressive threshold contagion on a Watts–Strogatz graph (n 1000, k 8, rewiring p swept 0 to 1): a person adopts once at least T neighbours have (T = 1 simple, 2 and 3 complex), synchronous rounds until nothing changes. Seeding rules at a fixed budget: random people, highest-degree people, a clustered patch (a node and its nearest neighbours), and spread-out seeds (greedy, pairwise graph distance >= 3). Not measured: the threshold T and k, which are swept.
Checks before the sweep (analytic): on the ring lattice (p = 0, k = 8) T = 2 spreads from a patch of 2 adjacent seeds and covers the ring, T = 1 from one seed covers any connected graph, and T larger than the degree never spreads. Compared with Centola and Macy (2007): complex contagion needs wide bridges, so a little rewiring (random shortcuts) should not help T = 2 from a single patch the way it helps T = 1.
H1: for T = 1, final adoption from one seed rises with p and reaches 1 by p = 0.1 (small world). Refuted if adoption at p = 0.1 is below 0.9 of its value at p = 1.
H2: for T = 2 from a clustered patch of 5, adoption is high (>= 0.9) at low p and falls toward 0 as p -> 1 (clustering lost); random seeds of the same budget do worse at low p but better at high p. Refuted if the patch is not the best rule at p <= 0.05.
H3: at T = 3 the tipping budget (smallest seed count that reaches >= 0.9) is larger than at T = 2 for every p, and hub seeding does not beat random seeding on a low-degree-variance graph (WS). Refuted otherwise.
Analysis: 20 graphs per cell, Wilson/normal 95% intervals on the mean adoption; nothing from real data, so conclusions are about simulated people under stated rules only.
Outcome (experiments/cascades/results.txt, 20 graphs per cell, seeds fixed): H1 not testable as stated: T = 1 from one seed covers all 1000 people at every p including the lattice (p = 0), so only speed, not the final fraction, depends on rewiring (rounds not reported). H2 half held: a clustered patch of 5 covers 1.00 at p <= 0.1 and 0.85 +- 0.16 at p = 0.3, the best rule at every low p; but the second half failed, at p = 1 no 5-seed rule gets past 1% (random 0.006), so there is no regime where random seeds beat a patch at this budget. Spread-out seeds never start anything at T = 2 (0.005 = the seeds themselves). H3 half held: the tipping budget at T = 3 exceeds T = 2 for every p (patch: 3, 3, 5 then none up to 40, against 2, 2, 3, 3, 8, 40 at T = 2). Hub seeding did beat random at T = 2 (budget 8 to 12 against 12 to 20 once p > 0), a refutation of the second half, probably because rewiring creates degree variance that raises the odds of two hubs sharing a neighbour; at p = 0 degrees tie, so hubs are random. Qualitatively matches Centola and Macy (2007) (complex contagion needs redundant, clustered ties); a quantitative check against their figures and Centola et al. (2018)'s 25% tipping point (a different model, a naming game) is not done.
Caveats: T, k and n are swept or chosen, not measured; WS graphs have no communities or heavy tails; random-seed cells have wide intervals because outcomes are bimodal (a seed pair either shares a neighbour or not).
2026-10-26 · Plan: contact that widens tolerance (milestone 2, written before the run)
Model: src/civ_levers/contact.py. Each agent has its own eps_i; an accepted cross-camp meeting raises eps_i by gain (cap 0.5), a repelled one lowers it by loss. Stand-in for Pettigrew and Tropp (2006); the gain and loss rates are not measured and are swept.
H1: with gain > 0, bridging at dose 0.25 merges the camps in settings where fixed-eps bridging merged none (eps 0.2 to 0.3, no backfire). Refuted if merged stays 0 of 10 at every gain up to 0.05.
H2: under backfire (tau 0.7) widening does not rescue bridging, since repelled meetings shrink eps (loss > 0) and repulsion pushes the camps apart faster than tolerance grows. Refuted if bridging with gain 0.02 merges at tau 0.7 with loss 0.02.
H3 (identity): if committed moderates are dismissed with probability q as the other camp's, a 20% moderate share stops merging by q = 0.5. Refuted if the merged count at q = 0.5 stays within one seed of q = 0.
Analysis: 10 societies per cell, same society as civ-levers (n 1000, 400 sweeps); merged = gap < 0.1; Wilson intervals on merged counts.
Outcome of the first run (accepted-contact rule): gain had no effect in any of 135 bridging cells (merged 0 of 10 everywhere, mean eps unchanged to two decimals). Reason: camps start 0.6 apart, so a cross-camp meeting is never within eps 0.2 to 0.3 and tolerance never gets a chance to grow; contact that only counts when already accepted is circular. H1 refuted for this rule. Under backfire eps collapses (floor reached) and extremes reach 0.6 to 1.0 as in civ-levers. Identity (H3): refuted for eps 0.3 (10 of 10 merge at every q to 0.75, the committed voices are heard a quarter of the time and that suffices); at eps 0.2 the 0.5 voice is out of reach anyway (0 of 10 at all q).
Follow-up logged before running: exposure rule (widen on any cross-camp meeting not repelled). H1b: this merges at least some no-backfire settings at gain >= 0.02. H2b: under tau 0.7 still no merge.
Outcome of the exposure run (experiments/contact/results.txt): H1b held, merged 10 of 10 in the no-backfire cells at gain 0.05 and bridging dose 0.25 for eps 0.2, 0.25, 0.3 (3 to 7 of 10 at dose 0.1). H2b held: 0 of 36 exposure cells merge at tau 0.7, though at tau 0.8 gain 0.05 merges 2 to 10 of 10. Caveat: gain rates and the mere-exposure rule are assumptions; next, calibrating them (milestone 6) and 2-D opinions.
2026-10-24 · First sweep: bridging backfires, a ladder within reach works
Built the two-camp model (bounded confidence plus repulsion) and checked it against Deffuant et al. (2000) cluster counts, the Hegselmann–Krause consensus threshold, the DeGroot and single-zealot fixed points and Jager–Amblard bipolarization.
Swept six levers over dose, ε 0.2–0.4 and τ none–0.6: bridging merges 2 of 25 settings and pushes more people to the extremes in 10; a ladder of committed voices merges 24.
A ladder spaced for a higher tolerance than the real one fails (0 of 10 at true ε 0.2, designed for 0.3); spacing for the least tolerant costs nothing.
Shipped the browser simulator (app/), checked against the Python model.
Next: contact that also widens tolerance; identity (a moderate seen as the other side's); two-dimensional opinions; calibrating ε and τ to panel data on attitude change.
Produced with AI assistance (Claude). Results are from the code in this repository and are simulated or stylised unless the abstract says otherwise; nothing here implies affiliation with or endorsement by any lab, researcher or company cited. MIT licensed.