asobi_vote_server (asobi v0.35.4)

View Source

Vote lifecycle state machine.

Manages a single vote instance within a match. States flow open -> closed (with automatic resolution on close). Supports plurality and approval voting methods, configurable timed windows, live or hidden tallies, veto, and tie-breaking.

Config keys

KeyTypeDefaultDescription
match_idbinary()requiredParent match ID
match_pidpid()requiredMatch server process
options[map()]requiredList of #{id, label} option maps
eligible[binary()][]Eligible voter IDs
window_mspos_integer()15000Vote window in milliseconds
methodbinary()"plurality""plurality", "approval", "weighted", or "ranked"
visibilitybinary()"live""live" or "hidden"
tie_breakerbinary()"random""random" or "first"
veto_enabledboolean()falseAllow eligible voters to veto
templatebinary()"default"Template name (resolved from app config if defined)
vote_idbinary()auto-generatedOverride vote ID
weightsmap()#{}Voter weights for "weighted" method: #{voter_id => number()}
max_revotespos_integer()3Max times a voter can change their vote
window_typebinary()"fixed""fixed", "ready_up", "hybrid", or "adaptive"
min_window_mspos_integer()5000Minimum window for "hybrid" mode
supermajorityfloat()0.75Threshold for "adaptive" early close
require_supermajorityboolean()falseIf true, winner must reach supermajority threshold or result is no-consensus
spectators[binary()][]Spectator voter IDs (separate pool)
spectator_weightfloat()0.3Weight ratio for spectator votes (0.0-1.0)
quorumfloat()0.0Min fraction of eligible voters needed (0.0 = disabled)
default_votesmap()#{}Default option per voter if they don't vote: #{voter_id => option_id}
delegationmap()#{}Vote delegation: #{delegator_id => delegate_id}

Vote templates

Define reusable templates in app config. Per-call config overrides template defaults:

{asobi, [{vote_templates, #{
    ~"boon_pick" => #{method => ~"plurality", window_ms => 15000, visibility => ~"live"},
    ~"path_choice" => #{method => ~"approval", window_ms => 20000, visibility => ~"hidden"}
}}]}

Vote methods

  • Plurality: each voter picks one option, highest count wins.
  • Approval: each voter submits a list of approved option IDs, highest approval count wins.
  • Weighted: like plurality, but each vote is multiplied by the voter's weight from the weights map. Unweighted voters default to 1.
  • Ranked: each voter submits a ranked list [first_choice, second, ...]. Iterative elimination: lowest first-choice option eliminated, its votes redistributed to next preference, until one option has majority.

Spectator voting

Spectators are a separate voter pool. Their tallies are merged with player tallies using spectator_weight (default 0.3 = 30% influence). Set spectators list and optionally spectator_weight.

Async voting

For non-real-time games. Set quorum (0.0-1.0) to resolve early when enough voters participate. Use default_votes for absent players and delegation to let a player's vote follow another's.

Window types

  • Fixed (default): vote runs for exactly window_ms, then closes.
  • Ready-up: closes as soon as all eligible voters have cast a vote, or when window_ms expires (whichever comes first).
  • Hybrid: like ready-up, but enforces a minimum min_window_ms before early close is allowed. Prevents snap decisions.
  • Adaptive: starts with window_ms, but when a supermajority threshold is reached, the remaining time shrinks to 3 seconds (giving others a last chance). Resets if supermajority is lost.

Grace period

Late votes arriving within 500ms after the window closes are still accepted to compensate for network latency.

Summary

Functions

Veto the vote (immediately cancels it). Only works if veto_enabled is true.

Cast a vote. Replaces any previous vote by the same voter during the window.

Return the current vote state including status, options, tallies (if live), and time remaining.

Start a vote server with the given config. See moduledoc for config keys.

Functions

callback_mode()

-spec callback_mode() -> gen_statem:callback_mode_result().

cast_veto(Pid, VoterId)

-spec cast_veto(pid(), binary()) -> ok | {error, term()}.

Veto the vote (immediately cancels it). Only works if veto_enabled is true.

cast_vote(Pid, VoterId, OptionId)

-spec cast_vote(pid(), binary(), binary() | [binary()]) -> ok | {error, term()}.

Cast a vote. Replaces any previous vote by the same voter during the window.

closed/3

-spec closed(gen_statem:event_type() | enter, term(), map()) -> gen_statem:state_enter_result(atom()).

get_state(Pid)

-spec get_state(pid()) -> map().

Return the current vote state including status, options, tallies (if live), and time remaining.

init(Config)

-spec init(map()) -> {ok, open, map()}.

open/3

-spec open(gen_statem:event_type() | enter, term(), map()) -> gen_statem:state_enter_result(atom()).

start_link(Config)

-spec start_link(map()) -> gen_statem:start_ret().

Start a vote server with the given config. See moduledoc for config keys.

terminate(Reason, StateName, State)

-spec terminate(term(), atom(), map()) -> ok.