
Tournament Lifecycle and Live Operations
Source:vignettes/tournament-lifecycle.Rmd
tournament-lifecycle.RmdThis guide is a function-discovery map for tournament operations. Every public function is shown in context so you know what it does, when to call it, and what you get back.
Mental model
A tournament definition reads like a rulebook. Stage-type functions
(round_robin, single_elim, swiss,
…) are the verbs — pipe them onto
tournament() or spec() to build the graph.
A tournament runtime feels like a scoreboard. Noun functions
(matches, standings,
stage_status, winner, …) expose live state
without mutating it.
1) Define
tournament() and spec()
tournament(participants) is the entry point for a live
runtime. It returns a tournament object that you pipe stage
verbs onto.
spec() is the entry point for a reusable blueprint
without participants. It returns a bracketeer_spec object;
use build(spec, participants) to materialize it later.
teams <- paste("Team", LETTERS[1:16])
# ── Live tournament ───────────────────────────────────────────────────────────
trn <- tournament(teams) |>
round_robin("groups")
# ── Blueprint / reusable spec ─────────────────────────────────────────────────
my_spec <- spec() |>
round_robin("groups") |>
single_elim("finals", take = top_n(8))tournament() and spec() accept the same
stage verbs. The difference is that tournament()
materializes the first stage immediately while spec()
defers everything to build().
Stage verbs
Each stage verb adds one node to the tournament graph. The verb name is the format; the first argument is a unique stage ID.
| Verb | Format |
|---|---|
round_robin(id, ...) |
All-play-all |
single_elim(id, ...) |
Single-elimination bracket |
double_elim(id, ...) |
Double-elimination bracket |
swiss(id, ...) |
Swiss-system |
two_leg(id, ...) |
Two-leg knockout |
group_stage_knockout(id, ...) |
Combined group + knockout |
Every verb accepts two wiring parameters:
-
from— source stage ID (defaultprevious_stage()). -
take— routing selector (default: all participants from source).
from = and previous_stage()
In a linear chain, from defaults to the immediately
preceding stage. You never write it explicitly.
# These two are identical:
tournament(teams) |>
round_robin("groups") |>
single_elim("finals", take = top_n(8))
tournament(teams) |>
round_robin("groups") |>
single_elim("finals", from = previous_stage(), take = top_n(8))For branching — two stages reading from the same source — you must
name from explicitly:
trn <- tournament(teams) |>
round_robin("groups") |>
single_elim("championship", from = "groups", take = top_n(8)) |>
single_elim("consolation", from = "groups", take = remaining())Routing helpers (take =)
Pass a selector to take = to control who advances from
the source stage.
top_n(8) # top 8 by overall standings
bottom_n(4) # bottom 4
slice_range(5, 8) # positions 5 through 8
remaining() # everyone not already consumed by a prior transition
losers() # eliminated participants
filter_by(function(df, ...) df[df$points >= 6, ]) # custom predicate
# ── Per-group variants (require grouped source stage) ─────────────────────────
top_per_group(2) # top 2 from each group
bottom_per_group(1) # bottom 1 from each group
slice_per_group(3, 3) # 3rd place from each group2) Validate (spec path)
validate(spec, n) runs preflight feasibility checks
before materializing anything.
my_spec <- spec() |>
round_robin("groups") |>
single_elim("finals", take = top_n(8))
validate(my_spec, n = 16) # passes
validate(my_spec, n = 6) # fails — can't select top 8 from 6 teamsErrors are identifier-rich: they include stage IDs and participant
counts. build() implicitly calls validate()
before materializing.
3) Build (spec path)
my_spec <- spec() |>
round_robin("groups") |>
single_elim("finals", take = top_n(8))
trn <- my_spec |> build(teams)build() materializes the first stage immediately and
leaves downstream stages in "blocked" state until
transitions resolve.
4) Enter results
result() — single match
-
stage— stage ID. -
match— match ID frommatches(). -
score— length-2+ numeric vector. For best-of series, a per-game score vector (e.g.c(3, 1, 2, 0, 3)for a 5-game series).
results() — batch from data frame
results_df <- data.frame(
match = 1:4,
score1 = c(2, 1, 3, 0),
score2 = c(1, 0, 2, 1)
)
trn <- trn |> results("groups", results_df)Columns must be match, score1,
score2. Batch entry triggers auto-advance when the final
result completes the stage.
Auto-advance
By default, entering the last match in a stage automatically
materializes all downstream stages — no explicit advance()
call needed.
Manual advance (opt-in)
trn <- tournament(teams, auto_advance = FALSE) |>
round_robin("groups") |>
single_elim("finals", take = top_n(8))
# ... enter results ...
trn <- trn |> advance("groups") # explicit triggerOverwrite policy
- Overwriting a result before downstream stages materialize is allowed; standings recalculate automatically.
- Overwriting a result after downstream materialization is blocked with an error that identifies the blocking downstream stage ID.
- Use
teardown()to un-materialize downstream stages and unlock the upstream result for editing.
5) Inspect state
All inspection functions return plain data frames or scalar values.
# ── Print ───────────────────────────────────────────────────────────────────
trn
# Tournament [2 stages]
# groups in_progress 90/120 matches
# finals blocked
# ── Stage overview ──────────────────────────────────────────────────────────
stage_status(trn)
# stage status complete total materialized
# groups in_progress 90 120 TRUE
# finals blocked 0 0 FALSE
# ── Matches ─────────────────────────────────────────────────────────────────
matches(trn) # all stages, pending (default)
matches(trn, "groups") # one stage, pending
matches(trn, "groups", status = "all") # one stage, all
# ── Standings ───────────────────────────────────────────────────────────────
standings(trn, "groups") # one stage
standings(trn) # all stages
# ── Outcomes ────────────────────────────────────────────────────────────────
winner(trn) # tournament winner (NA until complete)
rankings(trn) # final placement table
routing_log(trn) # transition audit trail6) Teardown
teardown(trn, stage) un-materializes a stage and all
downstream dependents, cascading along the DAG. Source-stage results and
standings are preserved.
trn <- tournament(teams) |>
swiss("open", rounds = 5) |>
single_elim("top_cut", take = top_n(8))
# ... enter all open results, top_cut materializes automatically ...
# Decide to revise an open result:
trn <- teardown(trn, "top_cut") # un-materializes top_cut
trn <- result(trn, "open", match = 3, score = c(0, 2)) # overwrite now allowed
# top_cut will re-materialize when open is completed again.After teardown, torn-down stages revert to "blocked" in
stage_status().
7) Full lifecycle example
library(bracketeer)
teams <- paste("Team", LETTERS[1:16])
# ── Define ────────────────────────────────────────────────────────────────────
trn <- tournament(teams) |>
swiss("open", rounds = 5) |>
single_elim("top_cut", take = top_n(8))
# ── Enter results ─────────────────────────────────────────────────────────────
open_ms <- matches(trn, "open")
for (i in seq_len(nrow(open_ms))) {
trn <- trn |> result("open", match = open_ms$id[i], score = c(1, 0))
}
# top_cut auto-materialized after final open result.
# ── Inspect ───────────────────────────────────────────────────────────────────
stage_status(trn)
matches(trn, "top_cut")
standings(trn, "open")
# ── Run top_cut ───────────────────────────────────────────────────────────────
cut_ms <- matches(trn, "top_cut")
for (i in seq_len(nrow(cut_ms))) {
trn <- trn |> result("top_cut", match = cut_ms$id[i], score = c(1, 0))
}
# ── Outcomes ──────────────────────────────────────────────────────────────────
winner(trn)
rankings(trn)
routing_log(trn)