Skip to content
Horulabs

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

  1. SeasonRules and uma are set
  2. EntryFour seats, raw scores
  3. PreviewThe engine runs live
  4. SavePoints frozen at entry
  5. StandingsOka derived, zero-sum
  6. ReturnPlayers read the table

The next hanchan is recorded

Every hanchan takes this path. Editing a season's rules moves future games only.

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.

BeforeAfter
  1. Formula copied down a sheet

    Pure scoring engine

    One function decides the preview and the saved result, so they cannot disagree.

  2. Rules edited in place

    Points frozen at entry

    A season's uma change applies to future hanchan and leaves history alone.

  3. Oka typed as a column

    Oka derived

    The leaderboard always sums to zero because the distribution is computed, not entered.

  4. One shared sheet

    A parlor's own table

    Multi-tenant by design: no shared player pool, and a non-member gets a 404.

  5. 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.

  1. 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.

  2. 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.

  3. Roles and tenancy

    Parlor admin, player and SURIMA super-admin. Every page and action passes a guard; row-level security is the backstop.

  4. Player surfaces

    Standings, full hanchan history, personal stats, level, badges and streaks. Players sign in with Google and ask a parlor to join.

  5. 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.

  6. 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.