SURIMA League
Standings a parlor can trust, because one engine does the math.
SURIMA League records a hanchan once, computes placement, uma, oka and chombo from the season's own rules, and keeps a leaderboard that always sums to zero.
How a hanchan runs now
6 steps
- SeasonRules and uma are set
- EntryFour seats, raw scores
- PreviewThe engine runs live
- SavePoints frozen at entry
- StandingsOka derived, zero-sum
- ReturnPlayers read the table
The next hanchan is recorded
A league lived in a spreadsheet.
One person typed four scores, copied the formula down, and hoped the uma column was right. Placement, oka and chombo were rules the sheet approximated, and a mistyped score stayed in the standings.
Every season changed the rules. When the sheet was updated, last season's results changed with it, because the formula was the only record of how they had been scored.
Players had no reason to open the sheet between sessions. The standings were a lookup, not a place to return to.
The rules moved into the season.
A leaderboard that is a spreadsheet with a web form on top cannot make these promises. The engine is the product.
Formula copied down a sheet
Pure scoring engine
One function decides the preview and the saved result, so they cannot disagree.
Rules edited in place
Points frozen at entry
A season's uma change applies to future hanchan and leaves history alone.
Oka typed as a column
Oka derived
The leaderboard always sums to zero because the distribution is computed, not entered.
One shared sheet
A parlor's own table
Multi-tenant by design: no shared player pool, and a non-member gets a 404.
A link nobody opened
A reason to return
Streaks, levels, badges, ELO and trophies sit beside the standings.
The math is the trust.
Three roles, real accounts, and one rule: never show a number the engine would not compute.
The scoring engine
Placement, uma, oka and chombo from the season's ruleset. Pure and tested, and the same module behind the entry preview and the saved result.
Season rules
Each season carries its own uma, starting points and oka. Points are frozen at entry, so history is not rewritten by a later edit.
Roles and tenancy
Parlor admin, player and SURIMA super-admin. Every page and action passes a guard; row-level security is the backstop.
Player surfaces
Standings, full hanchan history, personal stats, level, badges and streaks. Players sign in with Google and ask a parlor to join.
A parlor's own table
Nine color presets per parlor, dark mode that follows the OS, and public seasons a signed-out visitor can read.
Share surfaces
Standings cards drawn twice, for the canvas and the OG image, so a result travels into the community's group chats.
Built on the community's own rules.
Riichi terms stay Japanese in both languages because that is how players speak. The interface is bilingual by cookie and renders already translated.
- Next.js 16 · React 19
- Server-rendered standings, entry forms and Server Actions.
- Supabase
- Postgres, Google auth and storage for the parlor's own assets.
- Drizzle ORM
- Schema, migrations and typed queries.
- Vitest
- The scoring engine's tests, the product's real gate.
A result recorded once, a table that adds up.
League standings scored by each season's own rules, every result recorded once. A player can read the table on a phone between sessions and see exactly where they stand.
- The preview and the save share one function.
- A rule change moves future games, never the archive.
- Oka is derived, so the table sums to zero.
- Every parlor dresses its own table without forking the app.

Client
SURIMA
SURIMA is an Indonesian riichi mahjong community based in Surabaya. The League runs real parlor seasons at league.surima.id, alongside the community's academy and scoring apps.
Send one workflow. We'll look at it with you.
Tell us the task that costs your team the most hours, and how it runs.
The first conversation is free, with no commitment.