Estate-rule sim: contribution-credit arm (E7) #8

Merged
heimdall merged 1 commit from heimdall/credit-arm into main 2026-11-10 17:21:33 +00:00
6 changed files with 273 additions and 0 deletions

View file

@ -54,6 +54,17 @@ Static weight analysis, not a motion simulation: it measures weight share, not P
Limits specific to E6: the roll, the Pareto scale, b and unit are assumptions; the buyer joins as an additional patron and buys once (no repeated purchase or liquidation); decisiveness is estate-level only, not council-level (prior section 10 shows weight share is not decisiveness); the best split is a grid maximum, a lower bound; hours play no part in a purchase scenario because estate weights are fixed and the buyer sits in Patrons, so an hours buyer (a Member who is paid or who farms hours) is untested and the conversion of hours into weight is just a per-estate parameter whose gaming is not modeled.
## E7: contribution-weighted arm (SPEC section 5d)
Static weight analysis, not a motion simulation, and not a proposal: credit does not become vote weight here. Credit is linear in receipts (hours or credited changes), tallied per person, weight = min(credit, per-person cap), one function for a Meta-sized contributor and an ordinary member alike. Tally and half cap are E6's. Contributor share phi is the fraction of the Members estate's total raw credit; roll 15 and 150 ordinary members, 40 seeds, both estate-weight sets.
- **The half cap binds only when Members are the only live estate, and then it is a step at phi = .5.** All four estates live: 0 binding in every cell. Members + Minds live: 0 (the Members estate carries at most half the council, so the strict cap is never reached). Members alone: 0 for phi <= .4, and for phi >= .5 with no credit cap 1.000, because a contributor at exactly half of the credit has exactly half the estate (the cap is "strictly below"). The rate does not rise past phi = .5 because the cap binds as a yes/no, not by degree.
- **A per-person credit cap moves that step, it does not remove it.** Members alone, phi >= .5, rate of binding at 100x / 20x / 5x the ordinary median credit: 0.438 / 0.075 / 0.000 (pooled over both rolls and weights, 320 trials each). The bound is the cap against the size of the ordinary roll: 5x the median holds a contributor under half of a 15-member estate, which says the cap is doing the work the half cap would do, at the price of flattening every member above 5x median.
- **Splitting evades the half cap in every case it needed to act.** Members alone, over the whole sweep, k = 2, 10, 100: the joint share reaches .5 in 5,942 cases and in 5,942 no single identity binds (1.000). Uncapped linear credit makes splitting weight-neutral (multiplier 1.00) but still evades the per-identity half cap. With a per-person credit cap splitting also multiplies weight: at phi .9, k = 10, the multiplier is 3.58 at 100x, 9.66 at 20x, 10.00 at 5x (cap bites hardest on the single identity); k = 100: 6.50 / 21.58 / 59.10.
- **Falsifiers (SPEC 5d).** (1) A credit cap contains phi .9 with half cap never binding: only at 5x median; false at 100x and 20x. (2) Credit cap survives splitting at multiplier <= 1.1 at k = 10: false in every capped row (1.23 at phi .25 with a 100x cap up to 10.00). (3) Half cap evaded under splitting: true in all 5,942.
Limits: Pareto credit curve and its scale, the multiples of median, the Members placement, and the rolls are placeholders. A person-level cap needs an identity oracle (as E6); credit tied to merged receipts could provide one, which this sim does not test. Receipts are not audited for farming or purchase (hours-farming is the E6 hours gap, still unanalysed). Ordinary members' own credit is capped too, which matters only at 5x.
## Robustness (E5, E5b)
The sign of the PS effect holds across Users appeal 0.4/0.9/1.4, Users turnout .05/.10/.30 and Minds shared taste .3/.9/1.5. Its size scales with Users appeal loading (-0.025 at .4, -0.067 at 1.4): the rule helps in proportion to how appeal-driven the Users estate is, while its merit loss does not go away (MT gap -0.101 at .4 vs -0.063 at 1.4). If Users are not appeal-driven the rule is nearly all cost.

View file

@ -111,6 +111,22 @@ Serves the purchased-stake brief (operator relayed: Patron vote power is logarit
**Falsifiers fixed in advance.** (1) Working hypothesis "logarithmic weight bounds buyout power": refuted if a one-million-euro buyer's within-estate share s exceeds 1/2 at the harness's own roll, or if splitting multiplies weight by more than 2 at any k. (2) The half-influence cap is "binding" in a (cell, seed) iff x >= .5; the rate over the sweep is reported per live-estate scenario. (3) The cap is "evaded" by splitting iff the joint share is >= .5 while no single identity's share is.
## 5d. Contribution credit (revision 6, written before its results)
Serves `heimdall-body:follow_up:chat-mind-20261106T134754647Z-t14`. A static weight analysis like 5c, for a contribution-weighted arm. It informs Meta's later call and decides nothing: credit does not become vote weight, here or in any rule above.
**Credit.** A person's credit is the sum of their receipts (hours, or credited changes counted in the same linear unit), tallied per person. Weight is `min(credit, cap)`: linear, one function for every person, so a Meta-sized contributor is the same row of the same table as an ordinary member, with no exemption and no separate schedule. The per-person cap is a multiple of the ordinary members' median credit, swept over none, 100x, 20x, 5x (assumptions, not rulings).
**Contributor share.** The contributor holds a fraction phi of the estate's total raw credit (before capping), swept over .05 to .9. This is the "Meta-sized" axis. Placement is the Members estate (where 5c's hours channel lives), a PLACEHOLDER.
**Electorate.** 15 (the harness roll) and 150 ordinary members with Pareto(1.16) credit from 10 (prior section 4's contribution curve; the scale is mine), 40 seeds each, estate weights and live-estate scenarios as 5c.
**Same tally, same half cap.** `council_share` and `influence_cap` of 5c, unchanged: binds iff council weight share x >= .5.
**Splitting.** The contributor's credit split evenly over k = 2, 10, 100 identities. Reported per identity (binds), jointly (joint share >= .5) and as evasion (joint >= .5 with no identity binding), plus the weight multiplier against k = 1. With no credit cap, linear credit makes splitting weight-neutral; with a per-person cap it is not.
**Falsifiers fixed in advance.** (1) Per-person credit cap "contains" a large contributor if the half cap never binds at phi .9 with a cap in force in the Members-alone scenario. (2) Credit cap "survives splitting" if the weight multiplier at k = 10 is at most 1.1 with a cap in force. (3) The half cap is evaded under splitting if joint x >= .5 with no identity binding.
## 6. Strategic and adaptive agents
A fraction ρ = .25 of ballot holders and of absent undelegated roll members are strategic. Each is myopic about the motion at hand and best-responds to everyone else's current ballots over three rounds in random order:
@ -136,6 +152,7 @@ Paired across rules: one set of random draws per motion feeds all rules (common
- **E3f copy-the-ballot:** section 5b.
- **E4 appeal attraction:** β sweep at α = 1.5, c = .2.
- **E6 purchased stake:** section 5c. b x unit x Patrons roll x amount x estate weights x live-estate scenario, 40 seeds; splitting k sweep; candidate defences.
- **E7 contribution credit:** section 5d. phi x credit cap x roll x weights x live set x seeds; splitting k.
- **E5 robustness:** Users appeal loading b ∈ {.4, .9, 1.4}; Users base turnout {.05, .10, .30}; Minds ICC (s_Minds ∈ {.3, .9, 1.5}). This is the check on the assumed parameters in section 4.
## 8. Pre-registered falsifiers

View file

@ -580,3 +580,51 @@ def buyer_runs(amount, n, seeds, W, live, overrides=()):
wb = stake_weight("Patrons", euros=amount, overrides=overrides)
return [influence_cap(wb, sum(stake_weight("Patrons", euros=x, overrides=overrides) for x in stake_ladder(n, s)),
W, ESTATES.index("Patrons"), live) for s in range(seeds)]
# ---------------------------------------------------------------- contribution credit (SPEC section 5d)
#
# Credit is tallied per person from receipts (hours or credited changes, one linear unit each) and capped per
# person. The tally and the half-influence cap are the ones above (council_share, influence_cap). Credit is
# measured here as a candidate weight source only; this module never turns it into a vote.
CREDIT_SEED = 20261106
CREDIT_ESTATE = "Members" # PLACEHOLDER: where contribution credit would weigh; the hours channel above lives here
def credit_weight(credit, cap=None):
"""One person's weight from credit: linear in receipts, min(credit, cap). One function for every person,
a Meta-sized contributor included; there is no per-person exemption or schedule."""
return credit if cap is None else min(credit, cap)
def credit_ladder(n, s):
"""Seed s's n ordinary members' credit (hours): Pareto(PARETO_ALPHA) from PARETO_XM (prior section 4's
contribution curve; the scale is PLACEHOLDER)."""
return pareto_ladder(n, random.Random(CREDIT_SEED + 1000 * n + s))
def credit_cap_value(ladder, mult):
"""Per-person cap as a multiple of the ordinary members' median credit (None = no cap)."""
if mult is None:
return None
xs = sorted(ladder)
return mult * xs[len(xs) // 2]
def credit_run(phi, n, s, W, live, cap_mult=None, k=1):
"""Half-cap result for a contributor holding fraction `phi` of the estate's TOTAL raw credit, against n
ordinary members (seed s), the credit split evenly over k identities. Returns dict: the usual
influence_cap fields for ONE identity (`one`), the joint share (`joint_x`), `evaded` iff the joint
council share is >= HALF while no identity's is, and `w_total`."""
e = ESTATES.index(CREDIT_ESTATE)
lad = credit_ladder(n, s)
cap = credit_cap_value(lad, cap_mult)
rest = sum(credit_weight(x, cap) for x in lad)
big = phi / (1 - phi) * sum(lad)
wi = credit_weight(big / k, cap)
tot = k * wi
one = influence_cap(wi, rest + tot - wi, W, e, live)
joint = council_share(tot, rest, W, e, live)[1]
return dict(one=one, joint_x=joint, binds=one["binds"], joint_binds=joint >= HALF - EPS,
evaded=joint >= HALF - EPS and not one["binds"], w_total=tot, rest=rest, cap=cap)

View file

@ -879,6 +879,74 @@ Best multiplier at 1,000,000 EUR by log base and unit (b rows, unit columns):
|---|---|---|---|---|---|
| weight | 0.70 | 1.00 | 2.00 | 3.00 | 4.00 |
## E7 contribution-weighted arm: linear credit, per-person cap, the half-influence cap (SPEC section 5d)
Static weight analysis. One contributor holds a fraction phi of the Members estate's total raw credit (receipts: hours or credited changes, one linear unit); 15 and 150 ordinary members hold Pareto(1.16) credit from 10, 40 seeds each. Weight = min(credit, per-person cap), the same function for the contributor and everyone else. The tally and the half cap are E6's: council share x = W x s over the live estates; the cap binds iff x >= .5. Credit is not vote weight anywhere in this module; this table only says how often the half cap would have to act.
**Half cap binds, all four estates: fraction of (weights x roll x seed) by contributor share phi (rows) and per-person credit cap (columns); one identity, k = 1**
| phi | none | 100x median | 20x median | 5x median |
|---|---|---|---|---|
| 0.05 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.1 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.25 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.4 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.5 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.6 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.75 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.9 | 0.000 | 0.000 | 0.000 | 0.000 |
**Half cap binds, Members + Minds: fraction of (weights x roll x seed) by contributor share phi (rows) and per-person credit cap (columns); one identity, k = 1**
| phi | none | 100x median | 20x median | 5x median |
|---|---|---|---|---|
| 0.05 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.1 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.25 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.4 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.5 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.6 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.75 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.9 | 0.000 | 0.000 | 0.000 | 0.000 |
**Half cap binds, Members alone: fraction of (weights x roll x seed) by contributor share phi (rows) and per-person credit cap (columns); one identity, k = 1**
| phi | none | 100x median | 20x median | 5x median |
|---|---|---|---|---|
| 0.05 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.1 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.25 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.4 | 0.000 | 0.000 | 0.000 | 0.000 |
| 0.5 | 1.000 | 0.438 | 0.075 | 0.000 |
| 0.6 | 1.000 | 0.438 | 0.075 | 0.000 |
| 0.75 | 1.000 | 0.438 | 0.075 | 0.000 |
| 0.9 | 1.000 | 0.438 | 0.075 | 0.000 |
**Splitting: the contributor's credit split evenly over k identities. Members alone, equal weights, roll 15, mean over seeds. binds = the half cap acts on an identity; joint >= .5 = the combined share would have needed cutting; evaded = joint >= .5 and no identity binds; weight x = total weight / weight at k = 1**
| phi | cap | k=1: binds / joint>=.5 / evaded / weight x | k=2: binds / joint>=.5 / evaded / weight x | k=10: binds / joint>=.5 / evaded / weight x | k=100: binds / joint>=.5 / evaded / weight x |
|---|---|---|---|---|---|
| 0.25 | none | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.00 / 0.00 / 1.00 |
| 0.25 | 100x median | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.03 / 0.03 / 1.03 | 0.00 / 0.03 / 0.03 / 1.23 | 0.00 / 0.03 / 0.03 / 1.25 |
| 0.25 | 20x median | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.03 / 0.03 / 1.12 | 0.00 / 0.07 / 0.07 / 1.43 | 0.00 / 0.07 / 0.07 / 2.55 |
| 0.25 | 5x median | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.00 / 0.00 / 1.75 | 0.00 / 0.12 / 0.12 / 2.87 | 0.00 / 0.12 / 0.12 / 5.46 |
| 0.5 | none | 1.00 / 1.00 / 0.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.00 |
| 0.5 | 100x median | 0.88 / 0.88 / 0.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.08 | 0.00 / 1.00 / 1.00 / 1.31 | 0.00 / 1.00 / 1.00 / 1.88 |
| 0.5 | 20x median | 0.15 / 0.15 / 0.00 / 1.00 | 0.00 / 0.80 / 0.80 / 1.50 | 0.00 / 1.00 / 1.00 / 2.35 | 0.00 / 1.00 / 1.00 / 4.73 |
| 0.5 | 5x median | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.00 / 0.00 / 2.00 | 0.00 / 1.00 / 1.00 / 6.41 | 0.00 / 1.00 / 1.00 / 11.38 |
| 0.9 | none | 1.00 / 1.00 / 0.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.00 |
| 0.9 | 100x median | 0.88 / 0.88 / 0.00 / 1.00 | 0.00 / 1.00 / 1.00 / 1.93 | 0.00 / 1.00 / 1.00 / 3.58 | 0.00 / 1.00 / 1.00 / 6.50 |
| 0.9 | 20x median | 0.15 / 0.15 / 0.00 / 1.00 | 0.00 / 0.80 / 0.80 / 2.00 | 0.00 / 1.00 / 1.00 / 9.66 | 0.00 / 1.00 / 1.00 / 21.58 |
| 0.9 | 5x median | 0.00 / 0.00 / 0.00 / 1.00 | 0.00 / 0.00 / 0.00 / 2.00 | 0.00 / 1.00 / 1.00 / 10.00 | 0.00 / 1.00 / 1.00 / 59.10 |
Evasion rate over the sweep (phi x cap x k in 2, 10, 100 x live set x weights x roll x seed), cases where the joint share needed cutting:
| live estates | joint share >= .5 (k > 1) | of which no identity binds (evaded) |
|---|---|---|
| all four estates | 0 | 0 |
| Members + Minds | 0 | 0 |
| Members alone | 5942 | 5942 (1.000) |
## Claim tally over all cells (SPEC section 8)
supported 0, fails 2, undecided 233, n/a 0

View file

@ -353,6 +353,73 @@ def stake_section(quick):
return L
MEMBERS = E.ESTATES.index(E.CREDIT_ESTATE)
CLIVE = {"all four estates": (0, 1, 2, 3), "Members + Minds": (0, MEMBERS), "Members alone": (MEMBERS,)}
CREDIT_CAPS = (None, 100.0, 20.0, 5.0) # per-person credit cap, multiples of the ordinary members' median credit
def _capname(m):
return "none" if m is None else f"{m:g}x median"
def credit_section(quick):
"""E7 contribution-weighted arm (SPEC section 5d). Pure functions of fixed seeds; no wall clock."""
seeds = 4 if quick else 40
phis = (.1, .5, .9) if quick else (.05, .1, .25, .4, .5, .6, .75, .9)
rolls = (E.ROLL["Members"],) if quick else (E.ROLL["Members"], 150)
ks = (1, 2, 10, 100)
L = ["## E7 contribution-weighted arm: linear credit, per-person cap, the half-influence cap (SPEC section 5d)", "",
f"Static weight analysis. One contributor holds a fraction phi of the {E.CREDIT_ESTATE} estate's total raw credit (receipts: hours or credited "
f"changes, one linear unit); {rolls[0]}" + (f" and {rolls[1]}" if len(rolls) > 1 else "") +
f" ordinary members hold Pareto({E.PARETO_ALPHA}) credit from {E.PARETO_XM:.0f}, {seeds} seeds each. Weight = min(credit, per-person cap), the same function "
"for the contributor and everyone else. The tally and the half cap are E6's: council share x = W x s over the live estates; the cap binds iff x >= .5. "
"Credit is not vote weight anywhere in this module; this table only says how often the half cap would have to act.", ""]
for ln, live in CLIVE.items():
L += [f"**Half cap binds, {ln}: fraction of (weights x roll x seed) by contributor share phi (rows) and per-person credit cap (columns); one identity, k = 1**", "",
"| phi | " + " | ".join(_capname(m) for m in CREDIT_CAPS) + " |", "|---|" + "---|" * len(CREDIT_CAPS)]
for phi in phis:
cols = []
for m in CREDIT_CAPS:
hit = tot = 0
for W in E.WEIGHTS.values():
for n in rolls:
for s in range(seeds):
hit += E.credit_run(phi, n, s, W, live, m)["binds"]
tot += 1
cols.append(f"{hit / tot:.3f}")
L.append(f"| {phi:g} | " + " | ".join(cols) + " |")
L.append("")
L += ["**Splitting: the contributor's credit split evenly over k identities. Members alone, equal weights, roll "
f"{rolls[0]}, mean over seeds. binds = the half cap acts on an identity; joint >= .5 = the combined share would have needed cutting; "
"evaded = joint >= .5 and no identity binds; weight x = total weight / weight at k = 1**", "",
"| phi | cap | " + " | ".join(f"k={k}: binds / joint>=.5 / evaded / weight x" for k in ks) + " |", "|---|---|" + "---|" * len(ks)]
W = E.WEIGHTS["equal"]
for phi in (.25, .5, .9):
for m in CREDIT_CAPS:
cols = []
for k in ks:
rs = [E.credit_run(phi, rolls[0], s, W, (MEMBERS,), m, k) for s in range(seeds)]
base = [E.credit_run(phi, rolls[0], s, W, (MEMBERS,), m, 1)["w_total"] for s in range(seeds)]
cols.append(f"{sum(r['binds'] for r in rs) / seeds:.2f} / {sum(r['joint_binds'] for r in rs) / seeds:.2f} / "
f"{sum(r['evaded'] for r in rs) / seeds:.2f} / {sum(r['w_total'] / b for r, b in zip(rs, base)) / seeds:.2f}")
L.append(f"| {phi:g} | {_capname(m)} | " + " | ".join(cols) + " |")
L += ["", "Evasion rate over the sweep (phi x cap x k in 2, 10, 100 x live set x weights x roll x seed), cases where the joint share needed cutting:", "",
"| live estates | joint share >= .5 (k > 1) | of which no identity binds (evaded) |", "|---|---|---|"]
for ln, live in CLIVE.items():
j = ev = 0
for phi in phis:
for m in CREDIT_CAPS:
for k in ks[1:]:
for Wt in E.WEIGHTS.values():
for n in rolls:
for s in range(seeds):
r = E.credit_run(phi, n, s, Wt, live, m, k)
j += r["joint_binds"]
ev += r["evaded"]
L.append(f"| {ln} | {j} | {ev} ({ev / j:.3f}) |" if j else f"| {ln} | 0 | 0 |")
return L + [""]
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument("--quick", action="store_true")
@ -388,6 +455,7 @@ def main(argv=None):
L += [f"### {title.split(' ')[0]} / {name}", ""] + rule_table(r) + [""]
L += [pair_line(r), "", "PS by category threshold:", ""] + percat_table(r) + [""]
L += stake_section(a.quick)
L += credit_section(a.quick)
L += ["## Claim tally over all cells (SPEC section 8)", "",
f"supported {tally['supported']}, fails {tally['fails']}, undecided {tally['undecided']}, n/a {tally['n/a']}", ""]
if failing:

View file

@ -420,6 +420,67 @@ class StakeTests(unittest.TestCase):
self.assertTrue(all(x >= E.PARETO_XM for x in a))
PINNED_CREDIT_BINDS = {None: 320, 100.0: 140, 20.0: 24, 5.0: 0} # of 320 (phi .5, .9 x roll 15, 150 x 40 seeds x 2 weights)
class CreditTests(unittest.TestCase):
"""SPEC section 5d: contribution credit, per-person cap, the half-influence cap."""
M = E.ESTATES.index("Members")
W = E.WEIGHTS["equal"]
ALONE = (1,)
def test_one_standard_for_everyone(self):
for c in (3.0, 40.0, 1e6): # a Meta-sized credit gets no schedule of its own
self.assertEqual(E.credit_weight(c), c)
self.assertEqual(E.credit_weight(c, 25.0), min(c, 25.0))
def test_cap_is_per_person_and_a_multiple_of_the_ordinary_median(self):
lad = E.credit_ladder(15, 0)
self.assertIsNone(E.credit_cap_value(lad, None))
self.assertEqual(E.credit_cap_value(lad, 20.0), 20.0 * sorted(lad)[7])
def test_contributor_holds_phi_of_raw_credit(self):
r = E.credit_run(.5, 15, 3, self.W, self.ALONE)
self.assertAlmostEqual(r["w_total"], r["rest"]) # uncapped: phi .5 means equal to everyone else
self.assertAlmostEqual(r["one"]["s"], .5)
def test_half_cap_binds_at_half_of_credit_in_a_lone_estate_not_below(self):
self.assertTrue(E.credit_run(.5, 15, 0, self.W, self.ALONE)["binds"])
self.assertFalse(E.credit_run(.49, 15, 0, self.W, self.ALONE)["binds"])
def test_other_estates_dilute_the_contributor_below_the_half_cap(self):
self.assertFalse(E.credit_run(.9, 15, 0, self.W, (0, 1, 2, 3))["binds"])
def test_credit_cap_reduces_weight_and_can_release_the_half_cap(self):
un = E.credit_run(.9, 15, 0, self.W, self.ALONE)
cp = E.credit_run(.9, 15, 0, self.W, self.ALONE, cap_mult=5.0)
self.assertTrue(un["binds"])
self.assertFalse(cp["binds"])
self.assertLess(cp["w_total"], un["w_total"])
def test_splitting_is_weight_neutral_without_a_cap_and_gains_with_one(self):
base = E.credit_run(.9, 15, 0, self.W, self.ALONE)["w_total"]
self.assertAlmostEqual(E.credit_run(.9, 15, 0, self.W, self.ALONE, k=10)["w_total"], base)
c1 = E.credit_run(.9, 15, 0, self.W, self.ALONE, cap_mult=20.0)["w_total"]
self.assertGreater(E.credit_run(.9, 15, 0, self.W, self.ALONE, cap_mult=20.0, k=10)["w_total"], c1)
def test_per_identity_half_cap_is_evaded_by_splitting_credit(self):
r1 = E.credit_run(.9, 15, 0, self.W, self.ALONE, k=1)
r2 = E.credit_run(.9, 15, 0, self.W, self.ALONE, k=2)
self.assertTrue(r1["binds"] and not r1["evaded"])
self.assertFalse(r2["binds"])
self.assertTrue(r2["joint_binds"] and r2["evaded"])
def test_headline_numbers_are_pinned(self):
hit = {m: sum(E.credit_run(phi, n, s, w, self.ALONE, m)["binds"] for phi in (.5, .9) for n in (15, 150)
for s in range(40) for w in E.WEIGHTS.values()) for m in (None, 100.0, 20.0, 5.0)}
self.assertEqual(hit, PINNED_CREDIT_BINDS)
def test_deterministic(self):
a, b = E.credit_run(.5, 15, 2, self.W, self.ALONE, 20.0, 10), E.credit_run(.5, 15, 2, self.W, self.ALONE, 20.0, 10)
self.assertEqual(a, b)
class StrategyTests(unittest.TestCase):
def test_deviation_never_makes_agent_worse_off(self):
rng = random.Random(3)