Skip to content

Ban-pick: one-time pool veto for low-rated players (port of Botlatro's tuple veto) #14

Description

@ChronoFinale

What

Port Botlatro-Multiplayer's match veto into the in-game ban-pick draft: a
one-time-per-match button letting a low-rated player reroll the candidate pool,
with a difficulty-mercy bias.

Reference semantics (Botlatro-Multiplayer, verified in source)

  • Tuple-ban queues generate 9 weighted (deck, stake) tuples; teams ban from the list.
  • Veto (veto-tuples- handler): only match participants with
    elo <= queues.veto_mmr_threshold (fallback 200) may press it. Effect: full
    tuple-list regeneration with a mercy rule -- TupleBans.veto() forces white-stake
    generation when the list holds fewer than 4 white-stake options
    (vetoWhiteAmount = 4). One veto per match, shared (tupleVetoUsed keyed by
    match id): if both players are eligible, first press consumes it. No vote --
    unilateral, by design (it is a low-rated-player protection).
  • Contrast: Reroll Options is the consensual sibling -- any participant may
    trigger it but it runs a vote of all match players, also once per match.
  • Rank names (STONE etc.) are Discord roles (queue_roles.mmr_threshold) and are
    display-only; the veto gate is an independent raw threshold. The bot does not
    tie veto to rank either -- the numbers are just aligned by convention.

Two bot bugs the port should NOT copy

  1. Button visibility checks the WHOLE QUEUE, not the match:
    SELECT elo FROM queue_users WHERE queue_id = $1 + .some(...)
    (matchHelpers.ts) -- the VETO button renders whenever anyone in the queue is
    under threshold, and the real gate only happens at press time.
  2. Open TODO: the veto button disappears permanently after a reroll.

Design: server-computed eligibility (client never knows the rule)

The server computes can_veto per player per match and includes it in the
match/draft payload. The client renders the button only when true; the host/server
still validates the veto action itself (client-side gating alone is spoofable).

This keeps the eligibility RULE server-side and swappable: start with bot parity
(rating <= threshold in gamemode/queue config, using matchmaking_ratings.rating),
and later move to named tiers if/when the server grows a tier concept -- today the
schema has only integer ratings and leaderboard positions, no named ranks -- all
without touching the mod or API.

Depends on

#13 (weighted tuple pool policy) -- the mercy rule only has meaning against a weighted tuple pool. Eligibility and pool policy are both per modId + gameMode, so PvP and Speedrun configure independently.

Work items

  1. Server: veto threshold in gamemode/queue config; compute per-player
    can_veto into the match payload; validate + consume the veto action
    (once per match) server-side.
  2. API engine: veto action -- host regenerates the pool (consumer
    build_pool re-run with a mercy flag), resets the ban schedule, broadcasts;
    once-per-draft guard mirrored in host state.
  3. API UI: Veto button in the ban-pick button row (definition-time button
    config + per-frame check, same pattern as Confirm/Random); rendered only when
    the payload says can_veto; removed once consumed.
  4. Speedrun mod: pool builder honours the mercy rule (the white-stake-bias
    equivalent for its pools; engine already supports {key, stake} tuple items +
    decorate_tile).

Open questions

  • Threshold value per gamemode (bot default is 200; new-server ratings default 600
    -- numbers need re-basing).
  • Veto on plain deck drafts too, or only tuple (deck+stake) pools?
  • Repool semantics: bot restarts the tuple ban list -- mirror that (full draft
    restart) or preserve prior bans?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions