Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

/p/moul/kit/tally/v0

Directory · 4 Files
README.md Open

gno.land/p/moul/kit/tally

Order a scoreboard: highest score first, ties broken by key.

Fourth package of the p/moul/kit/* layer (moul/gno-contracts#151), after ui, store and num. kit composes the existing packages, it does not replace them.

Why it exists

gno has no sort.Slice. Ordering anything therefore needs a named type with Len, Less and Swap, so every realm that shows a leaderboard writes one.

Measured on main, 2026-09-28: 16 packages carry their own sort.Interface triple. They are not all the same shape, so here is the honest split, by how many score clauses the comparator has before its final return:

shape packages fits this package?
one score, then a tie-break kudos, leaderboard, multiset, pixelcanvas, prorata, reactions, streak, tamagotchi yes, 8 of the 16
two scores, then a tie-break collatz, idle, kingofdice, rpgroom, streaks, tipjar no
not a scoreboard eggling, semverdemo no

So this package targets 8, and it says so rather than claiming 16. A two-score board (idle ranks on prestige, then lifetime, then owner) has a genuinely domain-specific comparator, and flattening it into one int64 would be a worse API than the triple it replaced.

What the 16 disagree on: the tie

The final clause of Less is what decides two equal scores, and it is the clause everyone writes differently:

final clause realms
the key, as addr.String() idle, kingofdice, leaderboard, streaks, tamagotchi, tipjar
the key, as a bare string kudos, pixelcanvas, streak
a domain field (Elem, N, emoji, index) multiset, collatz, reactions, prorata
the score again rpgroom

rpgroom's third clause is return r[i].Kills > r[j].Kills, after Level and XP. That is not a total order: two heroes equal on all three compare false in both directions, so their order is whatever the sort algorithm did with the input. It is reproducible (heroes.Iterate walks an avl tree, and sort.Sort is deterministic), so this is not a consensus bug, but nobody chose that order and nothing pins it.

Sort always ends on Key, so the answer is the same one every time and it is explicable to a reader.

What is here

Sort(entries) in place, highest first, ties by key
Top(entries, n) the n highest, copied, so the caller's slice is untouched
Rank(entries, key) 1-based, 0 if absent, and never a tie: equal scores still get distinct places
Board NewBoard, Add, Set, Score, Has, Remove, Len, Entries, Top, Rank

Two entry points on purpose. The 16 above already own their state and adopt by mapping it to []Entry; a realm starting fresh reaches for Board and gets the storage too.

Board is backed by an avl tree, not a map. gno map iteration order is unspecified, so a Render built by ranging a map is not reproducible. TestBoardOrderDoesNotDependOnInsertion pins it.

What is deliberately not here

  • Rendering. A podium, a table and address shortening belong to kit/ui. This package returns rows.
  • Scores that are not int64. Nothing on chain ranks by a float, and gno has no float determinism worth relying on for consensus output.
  • Stable sort. Sort is a total order, so stability is not observable.

Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/p/moul/kit/tally/v0 dependency graph

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.