Battle Arena 3D Game Accounts
How players sign in to the 3D Battle Arena game with their account, and the profile and leaderboard API behind it.
Battle Arena 3D game: accounts, profiles and leaderboards
The 3D Battle Arena brawler (built from the separate battle-arena-3d repository) requires every
player to sign in with their José Madrid Salsa account before they can fight. This site owns those
accounts, the players' profiles and the leaderboards. The sales-driven team standings it sits beside
are described in Battle Arena Integration.
Where the game is served
The fundraising app serves the game at /battle-arena (https://fundraising.josemadridsalsa.com/battle-arena),
and each month's arena page (/arena/[period]) opens it with its Play now card. It is one static
file, apps/fundraising/public/battle-arena/index.html, with its link-preview image beside it:
next.config.mjsrewrites/battle-arenato that file and gives/battle-arena/*its own Content-Security-Policy, which lets the page reachwww.josemadridsalsa.com(this API), the game's own deployment (fundraising teams for its donate button), the CDN its sounds are on and the PeerJS server that links online rooms.app/battle-arena/share/route.tsis the page shared match links point to: its Open Graph tags name the winner, and it sends the visitor on to the game (into the room, for an invite).
To update the game, build it in the battle-arena-3d repository (its root index.html is the
build), then from apps/fundraising run node scripts/sync-battle-arena.mjs <path to that checkout>,
which copies the page and image and points the page's preview tags at this site. The game's own
deployment (the battle-arena-3d Vercel project) is published at https://battle.josemadridsalsa.com,
which the storefront's Battle Live nav tab and homepage Battle Arena section link to, and
still answers at https://battle-arena-3d-mauve.vercel.app.
Battle Live
The masthead's Battle Live tab (next to Shop, and in the mobile menu) opens the game. Its dot
blinks green while anyone is playing: it polls GET /api/arena/live every 30 seconds, which counts
players whose game checked in (POST /api/arena/friends/presence, about every 30 seconds while the
game is open and signed in) within the last 90 seconds. The homepage section shows the same count.
Signing in
- The game sends the browser to
/battle-arena/connect?return_to=<game page>&state=<random>. - A signed-out visitor goes through
/auth/signin(or/auth/signup) and comes back to the same page. - The visitor presses Play as …, which posts to
/api/arena/auth/authorize. - That route creates the account's
ArenaPlayeron first use, issues a game token and redirects toreturn_to#arena_token=<token>&state=<state>. The token is in the fragment, so it never reaches a server log; the game checksstateagainst the one it sent.
Tokens only go to listed game origins: the fundraising site (NEXT_PUBLIC_FUNDRAISING_SITE_URL), the
game's own deployment (battle.josemadridsalsa.com and its Vercel URL), plus any in ARENA_GAME_ORIGINS. Plain
http://localhost is accepted outside production for developing the game. The game keeps the token
for 90 days (ArenaPlayerSession, SHA-256 only) and sends it as Authorization: Bearer <token>.
Groups
A player's fundraising group (ArenaPlayer.teamId, a FundraiserTeam) comes from their account
when it can: the team of a FundraiserCharacter they claimed, else the team of the fundraiser their
FundraiserAccount runs. Those players cannot change it. Anyone else picks an ACTIVE team in the
game and can switch once every 7 days. Each match stores the group it was played for, so switching
never moves old results.
Recording matches
The game is peer to peer, so no server watches the fight. Instead the game opens an ArenaMatch
when the first round starts and closes it with the player's own result when the match ends. A
result is accepted once, only for an open match, within 3 hours, and only if the match could have
produced it (at least 6 seconds a round, no more knockouts than opponents allow, a damage ceiling).
A player can open at most 40 matches an hour. Closing a match adds it to the player's running
totals in the same transaction.
API
All routes live in apps/storefront/app/api/arena and the logic in apps/storefront/lib/arena-game.
| Route | Auth | Purpose |
|---|---|---|
POST /api/arena/auth/authorize | Site session (form) | Issue a game token and return to the game |
POST /api/arena/auth/sign-out | Game token | Revoke the token |
GET /api/arena/me | Game token | Profile: name, group, totals, rank, recent matches |
PATCH /api/arena/me | Game token | { handle?, teamId? } rename or choose a group |
GET /api/arena/teams | Public | Groups a player can choose |
POST /api/arena/matches | Game token | { mode, fighter?, opponents, room? } open a match |
POST /api/arena/matches/[id] | Game token | { won, roundsWon, rounds, knockouts, damage } close it |
GET /api/arena/leaderboard | Public | board=players|teams, period=week|month|all, mode=all|versus|cpu|online|tournament, teamId, limit |
GET /api/arena/players/[handle] | Public | A fighter's public profile |
GET /api/arena/live | Public | { playing }: players with the game open right now |
Signed-in routes answer cross-origin only to the listed game origins; the public ones to anyone.
Errors are { error, code? }, where code: "signed_out" tells the game to sign in again. Weeks
start Monday 00:00 UTC and months on the 1st (UTC), like the fundraiser battle months. Boards rank
by wins, then knockouts.
How is this guide?
Last updated on