/p/moul/x/games/prorata/v0
gno.land/p/moul/x/games/prorata/v0
A weighted split of an integer amount that adds up: Split, Share,
Total.
1import "gno.land/p/moul/x/games/prorata/v0"
2
3prorata.Split(100, []int64{1, 2, 3}) // [17 33 50], and it sums to exactly 100
4prorata.Split(10, []int64{1, 1, 1}) // [4 3 3], the dust goes to the lowest index
5prorata.Share(100, 1, 3) // 33, floored: "what would I get"
Every payout in a game is this operation: a pot, a roster, a weight each. Written
the obvious way, amount*weight/total per claimant, it is wrong in two ways that
only show up once there is real money in the contract.
The shares do not add up. Each one is rounded down, so their sum is short by
up to one unit per claimant. That dust either sits in the realm forever or lands
on whoever the loop happened to pay last, which is a rule nobody chose. Split
distributes by largest remainder: floor every share, then hand the leftover
units out one at a time to the claimants whose truncated fraction was largest,
ties going to the lower index. The result sums to exactly the amount, and the
rule is deterministic, so every node computes the same split and a Render of it
does not change between calls.
amount*weight overflows an int64 long before either factor does, which
turns a large payout negative. Nothing here ever computes that product: it splits
the amount into whole multiples of the total and what is left over, so
amount*weight/total becomes q*weight + r*weight/total with r already
smaller than the total. A pot of 10^18 ugnot across billion-unit weights is
exact, and what genuinely cannot fit returns ErrOverflow rather than wrapping.
Two smaller decisions, each with a test:
- A zero weight is paid nothing, including no remainder unit. Somebody with no claim is not handed dust.
- An empty roster, or one where every weight is zero, is an error, not an empty split. Silently returning nothing would strand the amount, and only the caller knows where it should go instead.
Nothing here reads the chain and nothing holds coins: the caller owns the roster and the custody.
Live game: r/moul/x/games/lastwords
· render it at /r/moul/x/games/lastwords/v0.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.