From 0f985c6003605c74badf089286bb8fd2bd142acd Mon Sep 17 00:00:00 2001 From: Heimdall Date: Fri, 6 Nov 2026 02:32:20 +0000 Subject: [PATCH] Estate-rule sim: pin the stake arm to heimdall-stake's golden vectors Copies CultLib PR #9 (head 84f932ec) tests/vectors/stake_golden.csv unmodified and checks log_weight and split_weight against all 26 rows. 25 match within rounding; the one sub-unit row differs by design: the arm's cliff at unit is Libby's min_amount_per_identity = unit, which the vectors do not set. Co-Authored-By: Claude Sonnet 5.5 --- .../2026-11-05-estate-rule-sim/estatesim.py | 4 +++ .../test_estatesim.py | 31 +++++++++++++++++++ .../vectors/stake_golden.csv | 29 +++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 docs/research/2026-11-05-estate-rule-sim/vectors/stake_golden.csv diff --git a/docs/research/2026-11-05-estate-rule-sim/estatesim.py b/docs/research/2026-11-05-estate-rule-sim/estatesim.py index 63acf4d..eba9812 100644 --- a/docs/research/2026-11-05-estate-rule-sim/estatesim.py +++ b/docs/research/2026-11-05-estate-rule-sim/estatesim.py @@ -473,6 +473,10 @@ def stake_params(name, overrides=()): def log_weight(x, b, unit): """w = 1 + log_b(x/unit): 1 at x = unit, 0 (not negative) below unit. + Equals CultLib heimdall-stake `weight` with floor 0, cap None and min_amount_per_identity = unit, which + is the cliff at unit; Libby's continuous log below unit is not used. Pinned to her golden vectors by + test_estatesim.StakeVectors (vectors/stake_golden.csv). + This is the weight of ONE IDENTITY. It is not sybil-safe: log is sub-linear, so the same x split over k identities weighs more (split_weight). Whether the log bounds a purchase depends on identities being scarce, which nothing in this module supplies. b and unit are PLACEHOLDER values (STAKE_BASE).""" diff --git a/docs/research/2026-11-05-estate-rule-sim/test_estatesim.py b/docs/research/2026-11-05-estate-rule-sim/test_estatesim.py index 5af6dce..3fb0d9f 100644 --- a/docs/research/2026-11-05-estate-rule-sim/test_estatesim.py +++ b/docs/research/2026-11-05-estate-rule-sim/test_estatesim.py @@ -506,5 +506,36 @@ class HarnessTests(unittest.TestCase): self.assertEqual(E.verdict((-.01, .05), (0.0, .02)), "undecided") + +class StakeVectors(unittest.TestCase): + """Pins the arm's log_weight / split_weight to CultLib heimdall-stake's golden vectors (PR #9, head 84f932ec, + copied unmodified to vectors/stake_golden.csv). The arm is Libby's function with min_amount_per_identity = unit, + so rows at or above the unit must match; a row below the unit must be 0 in the arm (the cliff) while the + vectors, which set no minimum, give the continuous value. Vectors are integer cents and micro-weights, the + arm is float and unit-agnostic: tolerance is 1 micro-weight per identity (Libby rounds each identity, the arm the total).""" + + @staticmethod + def rows(): + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vectors", "stake_golden.csv") + with open(path) as fh: + lines = [l.strip() for l in fh if l.strip() and not l.startswith("#")] + out = [l.split(",") for l in lines] + assert len(out) == 26 + return [(k, int(b), int(u), int(a), int(n), int(e)) for k, b, u, a, n, e in out] + + def test_every_vector_row(self): + for kind, b, u, a, k, expected in self.rows(): + with self.subTest(kind=kind, b=b, unit=u, amount=a, k=k): + if kind == "single": + got = E.log_weight(a, b, u) * 1e6 + if a < u: + self.assertEqual(got, 0.0) # arm's cliff = min_amount_per_identity = unit + continue + else: + got = E.split_weight(a, k, b, u)[0] * 1e6 + self.assertGreaterEqual(a // k, u) # every split row keeps each identity at or above unit + self.assertLessEqual(abs(got - expected), max(1, k)) + + if __name__ == "__main__": unittest.main() diff --git a/docs/research/2026-11-05-estate-rule-sim/vectors/stake_golden.csv b/docs/research/2026-11-05-estate-rule-sim/vectors/stake_golden.csv new file mode 100644 index 0000000..26f8978 --- /dev/null +++ b/docs/research/2026-11-05-estate-rule-sim/vectors/stake_golden.csv @@ -0,0 +1,29 @@ +# kind,base,unit_cents,amount_cents,k,expected_micro +# single: weight of one identity holding amount. split: budget (amount) split evenly over k identities (first amount%k hold one extra cent), summed. +# expected values computed independently with 60-digit decimal ln, rounded half up. No floor, cap or minimum. +single,10,100,100,1,1000000 +single,10,100,1000,1,2000000 +single,10,100,10000,1,3000000 +single,10,100,1000000,1,5000000 +single,10,100,50,1,698970 +single,10,100,10,1,0 +single,10,100,1,1,0 +single,2,100,400,1,3000000 +single,2,100,100,1,1000000 +single,2,1,1,1,1000000 +single,3,500,123456,1,6014537 +single,7,1,987654321,1,11643268 +single,10,1,1,1,1000000 +single,16,250,99999,1,3160960 +split,10,100,10000,1,3000000 +split,10,100,10000,2,5397940 +split,10,100,10000,10,20000000 +split,10,100,10000,100,100000000 +split,2,100,10000,1,7643856 +split,2,100,10000,2,13287712 +split,2,100,10000,10,43219280 +split,2,100,10000,100,100000000 +split,10,100,100000,1,4000000 +split,10,100,100000,2,7397940 +split,10,100,100000,10,30000000 +split,10,100,100000,100,200000000 -- 2.47.3