OpenVibe.Live/Docs
Source on GitHub

Arena — beefs, the board, and the ears#

The Arena tab (/arena) is where the platform's culture gets weaponised into entertainment. Nobody clicks "fight". Everything runs off what streamers say on mic (the continuous audio transcription) and what viewers do in chat:

With AI off everything still works: template headlines, fallback angles, keyword judges.

Speech policy#

The platform does not censor language. Offensive, provocative, taboo words — slurs included — are allowed, are never filtered, and are never a reason for the judge to score low. The only lines that don't count (server/arena/arena-service.js isBannedText, plus the judge's flagged) are behaviour: direct threats of violence (kys, "I'll kill you"), sexual content about minors, and doxxing (addresses, phone numbers). Those lines are dropped from quotes, feeds and scoring; nothing else is.

Beefs (server/arena/beef.js)#

RuleValue
Opens whenthe listener judges ≥ 20 words in the 45 s after a fighter's name was said as aimed at them (roast, callout, disrespect, brag over them)
Clockthe other side must answer on their own stream within 15 min if live, 24 h if offline (an offline clock is tightened to 15 min the moment they go live)
Answerany judged hit from the side on the clock; every hit flips the clock to the other side
Forfeitclock runs out → the silent side loses
Hard end24 h after opening → higher total wins (total = Σ hit quality (0–10, judged) + crowd hype (max 10)), equal = draw
Upsetwinner ranked ≥ 4 places below the loser (+20 XP)
Rematchsame pair again → flagged, with the rivalry's record and receipts (best lines from earlier beefs)
XP+5 open · +quality per hit (×2 while a bounty is on the target) · +40 win
Crowd!hype / Hype button: one per person per side (no voting)

Headlines (open + result) are AI-written (chat role) with templates as fallback; the announcer's one-liner per hit comes from the judge. Streaks, upsets and the head-to-head history are shown on every beef card.

The board (server/arena/board.js) — living lore#

No voting anywhere. The board is a list of subjects the community is actually on about, each a lore profile that writes itself:

The ears (server/arena/listener.js)#

Every 15 s, for every roster stream that is live and has a transcript line in the last 30 min:

  1. New stream_timeline_events speech lines are read.
  2. Name detection (server/arena/names.js) — every roster name (username, display name, fighter name, plus the persona's AI-written spoken_as nicknames/mishearings) is expanded into the forms a transcriber produces: camelCase/snake/kebab split (JapaneseOldGuy → "japanese old guy"), digits and _tv/xX_ decorations dropped (lofi_dan99 → "lofi dan"), leet undone, glued forms. Lines are matched exact → fuzzy (edit distance scaled by length, same word count only: "Matticus", "japanese old gai") → phonetic (compact metaphone key: "goose lee", "Goosley", "mattie cuss", "pixel queen"). Single common words can never be aliases, prefixes don't count, the speaker never matches themselves.
  3. Focus lock — a name-drop locks the ears on that fighter for 2 min; everything said afterwards goes to the beef judge together with what was already said about them, and the judge decides about_target (still on them via "he", "her chat", "that guy", the same story) and aimed_at_target. Every hit extends the lock 3 min (hard cap 20 min without a fresh name-drop); neutral talk keeps it a bit; two chunks about something else drop it. A different name-drop switches targets (the pending chunk is judged first). The console (/arena/live/<user>) shows the lock, its context and whether the last hit was a continuation.
  4. Lines matching a board subject's keywords become on-mic moments (and auto-join the fighter). Other lines pool for the subject judge against the streamer's active subject — or the one they just brought up (≥ 20 words, ≥ 30 s between calls): on_topic, quality, best_line, about.
  5. One judge call per stream per tick; keyword heuristics when AI is off (pronouns + spice count as a continuation).

/arena/live/<username> shows exactly what the ears hear for a fighter: hot mic lines, the last judgements, active topic progress, open beefs and any bounty on them (auto-refreshes; a level-up flashes).

Pages#

/arena — pulse · on the mic now · open + settled beefs · the board (subject cards: headline, subject + tagline, thread chips with counts, latest line, mention counts, fighters; first card featured; cooled-off list) · Trash Level ladder · yappers · power ladder (rows expand; compact on phones) · rules. /arena/beef/<id> — tale of the tape, tug-of-war, live clock, ringside feed (every judged hit with ▶ to the VOD second and 🔊), receipts from earlier beefs, hype/side buttons. /arena/topic/<id> — headline, subject + tagline, counts, story so far, thread tabs filtering the moment timeline (chat + on-mic with ▶ to the VOD second), fighters heard on it with their best line, loudest chatters with yap levels. /arena/<username> — stats drill-down, voice + quotes, Trash Level card, rivalries with receipts, beef history.

Chat commands (server/arena/arena-chat.js)#

!topic <text> (signed in; 1 per person + per IP per 24 h; AI rewrites it) · !bounty <user> (1 per hour, same 24 h rule) · !hype (the streamer's newest open beef, else their active subject) · !beef · !board · !arena [user]. One command per person every 4 s; replies are private system lines, milestones go to the room.

API (/api/arena)#

MethodPathNotes
GET/status · /fighters · /fighters/:user · /fighters/:user/stat/:stat · /liveroster, card (+ beefs, rivalries, level, active_topic), drill-down, live fighters (hot mic, active topic, open beefs)
POST/fighters/:user/refreshadmin — regenerate persona (+ portrait)
GET/console/:userthe ears: listener state, hot mic, active topic + my progress, open beefs, bounty
GET/boardopen (by heat, each with lore/keywords/mentions/fighters/last + best moment), archive, pulse, levels, yappers
POST/board/topics {text} · /board/bounty {username}auth; 1 per person + per IP per 24 h; the AI rewrites the text
GET/board/topics/:iddetail incl. moments (80), best_lines, top_chatters, fighters with their best moment
POST/board/topics/:id/join · /leave (auth, roster) · /hype {user_id}
POST/pulse/refresh · /board/topics/:id/loreadmin — force discovery + lore sweep / rebuild one subject's lore
GET/beefs · /beefs/:id
POST/beefs/:id/hype {side}one per person per side; anonymous by hashed IP
GET/levels · /yappersladders

Settings#

KeyDefaultEffect
arena_enabledtruefalse → API 404s
ai_image_enabled / ai_image_model / ai_image_quality / ai_image_cost_usdfalse / gpt-image-1 / low / 0.011portraits
arena_vote_saltJWT_SECRETsalt for anonymous voter hashing

Background job (server/arena/arena-job.js): personas/portraits every 20 min (bounded, budget-aware) · listener every 15 s · clocks + bounty expiries + chat scan every 60 s · discovery ≤ 1 AI call / 5 min and only with new material · lore rewrites only for subjects with ≥ 3 new moments (≤ 3 per minute).

Personas: the voice comes from their chat history first — their own chat lines with the room they typed in, the chat AI's profile of them as a chatter (overview, long-term memory, timeline), then the streaming profile as flavor. Output: taunt + three more taunts (ragebait in that exact typing style), typing_style, spoken_as (how people say/mishear the name — fed to name detection) and custom_stats (six stats unique to them, e.g. "Alt Accounts 91", drawn as their own radar on the ladder row and profile).

Tests: node test/arena.test.js (roster, ratings, filter, quotes) · node test/arena-beef-board.test.js (beefs, board, listener tick, chat commands, API) — both on a temp DB, no AI needed.

Voice + portraits#

Yappers — profiles for everyone who chats (server/arena/chatters.js)#

One polymorphic key per chatter — user:<id>, anon:<N> (the site's IP-derived anon<N> identity) or relay:<platform>:<name> (chat relayed from Twitch/Kick/YouTube/RobotStreamer) — so anonymous viewers and relayed chatters get profiles too. No extra AI calls for XP: it falls out of the board pipeline.

XPfor
+3 / +6a chat line that lands as a moment on a subject (double when the subject is HOT)
+4the first time you touch a subject
+15starting a subject (!topic / the page)
+10being quoted in a subject's lore (name appears in the rewritten lore)
+1hyping a fighter on a subject
+5 × streak (≤ 7)the first moment of each day; streak = consecutive days

Level = 1 + floor(√(XP / 25)) (the game's curve). Titles: Lurker → Chatter (2) → Yapper (3) → Instigator (5) → Ragebaiter (7) → Menace (9) → Main Character (12) → Community Consciousness (15) → Final Boss (20). OpenVibe accounts bank level × 10 OpenCoins on every level-up through the network wallet (idempotent key live:arena_yap_level:<user>:<level>; unlinked accounts are simply skipped); anons/relay chatters keep the level and the title. Level-ups are announced in the room the moment happened in.

Yap card — at level ≥ 3, at most once a day and only when ≥ 25 XP changed, the AI writes a card (title, blurb, catchphrase in their typing style, "known for", archetype) from the chat AI's existing profile of them (chat_ai_summaries user/anon/relay) + their best lines and subjects; ≤ 4 cards per housekeeping minute.

Where it shows: the Yappers block on /arena (podium with level rings, streak flames, cards; weekly movers), /arena/chatter/<key> (card, XP bar, titles ladder, moments, subjects, what the chat AI has on them), and the chat context menu (click a name → yap level + title + bar, linking to the page). API: GET /api/arena/yappers, GET /api/arena/chatter/:key, GET /api/arena/chatter/by-user/:username, admin POST /api/arena/chatter/:key/card.

Coming back: your arena, check-ins, notifications#

Growth: tiers, achievements, history (server/arena/progress.js)#

Fighter page order: hero (portrait, POWER, tier, record, taunt) → progress panel (tier bar, achievements grid, history) → Trash Level card → characteristics radar + quips → rap sheet (lore, signature/special/weakness, walk-out, catchphrase, typing style) → ragebait bubbles (🔊 in their chat voice) → on the mic (quotes) → the numbers (bars) → rivalries → beefs.

This page is rendered from docs/arena.md in the OpenVibe.Live repository. Found a mistake? Edit it there.