Skip to content

Managing Tiebreakers

MockScores lets organizers control how teams are ranked when results are published. You can start with a template or build a custom ranking system using either the visual Blockly editor or through a text based editor.

Both editors allow for the same levels of configuration. The visual editor is recommended for most organizers, while the text editor is useful for managing complex rules and copying over configurations.

The playground below is the same editor used in the app, running entirely in your browser on sample data. Drag blocks or edit the configuration text, and watch the standings re-rank in real time. Nothing here is saved.

  1. Open your tournament from the organizer dashboard.
  2. Select Structure
  3. Select Manage Tiebreakers.

The editor is divided into three parts:

  • Dummy Data Preview shows a preview of what the standings would look like if applied on dummy data. The preview will update automatically as the configuration is changed.
  • Statistics define the values MockScores calculates for each team.
  • Tiebreaker order determines which statistics are compared first.

MockScores compares teams using the statistics in your tiebreaker order.

There are 2 types of statistics that are calculated.

  • Intermediate Statistics are calculated at the pairing level. This is useful for determining if a round was a win for one side.
  • Aggregated Statistics are calculated across the whole tournament. These statistics could be things like the total number of points a team has scored or a team’s Competitive Strength.

Tiebreakers are only valid on Aggregated Statistics.

MockScores will look at the defined tiebreakers. It will then sort the teams based on the first tiebreaker. It will go through all the tiebreakers until there are none remaining. Teams will be sorted at the end by their team code.

For example, a tournament might rank teams by:

  1. Ballots won
  2. Total points
  3. Presider selections
  4. Point differential

In this configuration, total points are only considered when two teams have the same number of ballots won. Presider selections are only considered when both ballots won and total points are tied.

Templates provide a ready-to-use configuration for common tournament rules.

  1. Select the template that most closely matches your tournament’s rules.
  2. Review the statistics and their order.
  3. Use the preview to confirm that the results are ranked as expected.
  4. Save the configuration.

You can customize a template after selecting it. This is often easier than starting from an empty configuration.

Blockly is a block based programming tool similar to MIT’s Scratch.

MockScores allows organizers to define tiebreakers through Blockly, allowing for an easy-to-use and interactive experience.
Blockly lets you create a configuration by connecting visual blocks. Blocks only connect where their values are compatible, which helps prevent invalid rules.

Create one statistic for each value that should appear in the standings or be used to rank teams.

  1. Add a statistic block.
  2. Give the statistic a clear name, such as Ballots Won or Point Differential.
  3. Connect the blocks that calculate its value.

Statistics can use information recorded for each pairing, including:

  • Points earned by the team
  • Points earned by the opposing team
  • The number of scorers assigned to the pairing
  • Whether the team received the presider’s tiebreaker selection

Depending on your tournament’s rules, you can total values across rounds, calculate averages or percentages, compare values, and combine multiple calculations.

After defining your statistics, add them to the tiebreaker list in priority order. The statistic at the top is compared first.

Use clear display labels because these names also help organizers and coaches understand the published standings.

The DSL editor provides direct access to the same configuration created by Blockly. It uses a Lisp-like syntax, where each expression is enclosed in parentheses and begins with an operation.

Use the DSL editor when you need precise control, want to review the generated configuration, or are comfortable editing structured text.

Keep these rules in mind:

  • Parentheses must remain balanced.
  • Give each statistic a unique name.
  • Define every statistic referenced by a column or tiebreaker.
  • The order of the ranking rules determines their priority.
  • Display labels should be understandable to people viewing the standings.

Changes made in either editor affect the same configuration. After editing the DSL, return to the visual view to confirm that the rules still appear as intended.

This section documents the serialized DSL for advanced users. The DSL is the source of truth stored by MockScores. Blockly translates between visual blocks and this format, while standings calculations interpret the DSL directly.

  • Whitespace between tokens is ignored.
  • Parentheses delimit S-expressions.
  • Keywords, field names, aggregation names, and sort directions are bare atoms.
  • Statistic names and labels should be quoted strings so they can contain spaces.
  • Numbers are bare numeric atoms, such as 0, 3, -1, or 2.5.
  • String literals cannot be used as numeric expressions.
  • Names produced by the serializer escape embedded quotation marks and backslashes.

Although the parser accepts a bare atom wherever it expects a name, quoted names are recommended for consistent round-tripping.

The notation below uses * for zero or more repetitions, + for one or more repetitions, | for alternatives, and brackets for an optional value.

config ::= "(" "config" config-entry* ")"
config-entry ::= stat-def
| team-stat-def
| intermediate-def
| trimmed-def
| columns-def
| tiebreakers-def
stat-def ::= "(" "stat" name aggregation expression ")"
team-stat-def ::= "(" "team-stat" name expression ")"
intermediate-def
::= "(" "intermediate" name aggregation expression ")"
trimmed-def ::= "(" "trimmed" name aggregation number expression ")"
columns-def ::= "(" "columns" column-def* ")"
column-def ::= "(" "column" name [name] ")"
tiebreakers-def
::= "(" "tiebreakers" [rank-method] tiebreaker-def* ")"
tiebreaker-def ::= "(" "by" name order ")"
| "(" "h2h" name order ")"
| "(" "alpha" alpha-field order ")"
| "(" "when-tied" integer integer tiebreaker-def+ ")"
aggregation ::= "sum" | "avg" | "max" | "min" | "count"
order ::= "asc" | "desc"
rank-method ::= "average" | "min" | "max" | "first" | "dense"
alpha-field ::= "code" | "name"
integer ::= non-negative-integer
name ::= quoted-string | bare-atom
expression ::= number
| "(" "pairing" bare-atom ")"
| "(" "ballot" bare-atom ")"
| "(" "team" bare-atom ")"
| "(" "stat" name ")"
| "(" "intermediate" name ")"
| "(" "opponent" name ")"
| "(" binary-op expression expression ")"
| "(" "if" expression expression expression ")"
| "(" unary-op expression ")"
binary-op ::= "+" | "-" | "*" | "/" | "**"
| "=" | "!=" | "<" | "<=" | ">" | ">="
| "and" | "or"
unary-op ::= "sqrt" | "abs" | "neg" | "ln"
| "log10" | "exp" | "pow10"

An empty or whitespace-only document is treated as an empty configuration. Any nonempty document must have (config ...) as its root form.

Form Evaluation Purpose
(stat "Name" AGG EXPR) Evaluates EXPR for each pairing, then applies AGG Defines a regular team statistic
(team-stat "Name" EXPR) Evaluates once using the team’s calculated statistics Defines a statistic derived from team-level values
(intermediate "Name" AGG EXPR) Evaluates per pairing and aggregates, but is intended for reuse rather than display Stores a reusable intermediate calculation
(trimmed "Name" AGG TRIM EXPR) Removes TRIM values from each end before aggregation Defines a trimmed statistic
(columns ...) Does not affect calculation Selects and labels statistics shown in standings
(tiebreakers ...) Evaluated from first to last Defines the ranking sequence

TRIM is parsed as a number. Use a nonnegative integer so the number of values removed from each end is unambiguous.

Aggregation Result
sum Adds the per-pairing values
avg Returns their arithmetic mean
max Returns the greatest value
min Returns the least value
count Counts the evaluated entries

A team-stat does not include an aggregation token. Internally, it is marked as team-level and stored with sum as its aggregation value, but its expression is evaluated once against the team’s statistics.

Expression Meaning
(pairing FIELD) Reads FIELD from the current pairing value
(ballot FIELD) Alias of (pairing FIELD) in the current implementation
(team FIELD) Reads a built-in value from the current team’s statistics
(stat "Name") Reads a previously calculated statistic
(intermediate "Name") Reads an intermediate calculation
(opponent "Name") Reads a named statistic for the opposing team

FIELD is validated by the parser. (pairing FIELD) and (ballot FIELD) must name a known per-pairing field, and (team FIELD) must name a known team-level field; an unknown field is a parse error, not a silent runtime undefined. The available fields are:

  • Per-pairing fields ((pairing ...) / (ballot ...)): ballots_won, ballots_lost, ballots_tied, points_for, points_against, won_presider_tb, ballot_pf, ballot_pa, ballot_pd, ballot_raw, num_ballots, num_scorers.
  • Team-level fields ((team ...)): ballots_won, ballots_lost, ballots_tied, points_for, points_against, won_presider_tb, ballot_pf, ballot_pa, ballot_pd, ballot_raw, num_scorers, num_pairings.

(stat ...), (intermediate ...), and (opponent ...) references are also checked: the name must resolve to a declared statistic (or, for intermediate, a declared intermediate). An undefined reference is a parse error.

References create dependencies between calculations. Define foundational per-pairing statistics and intermediates before team-level formulas that consume them. Cyclic references cannot produce a valid calculation.

Arithmetic operators use prefix notation:

(+ 4 2)
(- (pairing points_for) (pairing points_against))
(* (stat "Ballots Won") 2)
(/ (stat "Total Points") (team num_pairings))
(** (stat "Win Rate") 2)

The supported arithmetic operators are +, -, *, /, and **. Each accepts exactly two operands. Use (neg EXPR) for explicit unary negation.

Comparison and logical expressions return the number 1 when true and 0 when false:

(> (pairing points_for) (pairing points_against))
(and (> (stat "Ballots Won") 0) (> (stat "Point Differential") 0))
(if (= (pairing won_presider_tb) 1) 1 0)

The supported comparisons are =, !=, <, <=, >, and >=. Logical expressions support and and or. The conditional form is (if TEST THEN ELSE).

Math functions accept one expression:

Form Calculation
(sqrt x) Square root
(abs x) Absolute value
(neg x) Negation
(ln x) Natural logarithm
(log10 x) Base-10 logarithm
(exp x) Euler’s number raised to x
(pow10 x) 10 raised to x

Calculations use JavaScript number semantics. Operations such as division by zero or a logarithm outside its domain can produce Infinity or NaN, which may lead to unexpected standings.

Columns control presentation separately from calculation:

(columns
(column "Ballots Won" "Ballots")
(column "Total Points" "Points")
(column "Point Differential" "PD"))

The first name identifies the statistic. The second name is the displayed column label. If the label is omitted, the statistic name is used. A statistic can participate in ranking without being displayed, and a displayed statistic will not become a tiebreaker unless defined in the tiebreaker order.

(tiebreakers
(by "Ballots Won" desc)
(h2h "Ballots Won" desc)
(by "Point Differential" desc)
(by "Points Allowed" asc)
(alpha code asc))

Each rule refines the ordering within groups of teams that are still tied after the preceding rules:

  • (by "Stat" desc) ranks the greater value first.
  • (by "Stat" asc) ranks the smaller value first.
  • (h2h "Stat" ORDER) applies a conditional head-to-head comparison using the named statistic.
  • (alpha code ORDER) / (alpha name ORDER) orders alphabetically by team code or team name. This is useful as a final, deterministic rule.

by may only reference a defined statistic (a stat, trimmed, team-stat, or team-level builtin). It cannot reference an intermediate, because intermediates are per-pairing scratch values that are never aggregated onto the team row — aggregate it into a stat first, or use h2h, which recomputes the intermediate for the specific head-to-head pairing.

Rules are evaluated in the order written. The serializer preserves this order.

(when-tied MIN MAX RULE...) applies its nested rules only to tie-groups whose size is within the inclusive range [MIN, MAX]. MIN and MAX are non-negative integers and MAX must be greater than or equal to MIN. This lets you resolve, say, only two-way ties with head-to-head while leaving larger groups to a different rule.

(tiebreakers
(by "Ballots Won" desc)
(when-tied 2 2
(h2h "Ballots Won" desc))
(by "Point Differential" desc))

when-tied blocks may nest other rules, including further when-tied blocks, and must contain at least one rule.

(tiebreakers [METHOD] RULE...) accepts an optional leading rank-assignment method that controls how teams still tied after every rule are given rank numbers. It is modeled on pandas DataFrame.rank(method=...) and defaults to first when omitted.

(tiebreakers dense
(by "Ballots Won" desc)
(by "Point Differential" desc))
Method Rank assignment for a tie-group
first Tied teams keep input order; ranks are strictly sequential (no shared ranks). This is the default.
min Every tied team gets the lowest rank in its group, leaving gaps after.
max Every tied team gets the highest rank in its group, leaving gaps after.
average Every tied team gets the average of the group’s ranks.
dense Like min, but the next group’s rank is only one higher (no gaps).
(config
(stat "Ballots Won" sum
(> (pairing points_for) (pairing points_against)))
(stat "Total Points" sum
(pairing points_for))
(stat "Point Differential" sum
(- (pairing points_for) (pairing points_against)))
(stat "Presider Selections" sum
(pairing won_presider_tb))
(team-stat "Scoring Index"
(/ (stat "Total Points")
(+ (stat "Ballots Won") 1)))
(columns
(column "Ballots Won" "Ballots")
(column "Total Points" "Points")
(column "Point Differential" "PD")
(column "Presider Selections" "Presider"))
(tiebreakers
(by "Ballots Won" desc)
(by "Total Points" desc)
(by "Presider Selections" desc)
(by "Point Differential" desc)
(alpha code asc)))

The field names in this example are drawn from the per-pairing and team-level field sets documented above. An unknown field is rejected at parse time, so the preview will surface the error before you save.

The parser reports errors for:

  • A nonempty document without a (config ...) root
  • A config entry that is not a list
  • An unknown config entry or expression operator
  • An operator or form with the wrong number of operands
  • An invalid aggregation, sort direction, rank method, or alpha field
  • An unknown per-pairing field in (pairing ...) / (ballot ...) or team field in (team ...)
  • A (stat ...), (intermediate ...), or (opponent ...) reference to a name that was never defined
  • A (by ...) rule that references an intermediate rather than a stat
  • A duplicate statistic or intermediate name
  • A when-tied block whose max is less than its min, or that contains no rules
  • A non-integer or negative trimmed count or when-tied bound
  • A quoted keyword where a bare keyword is required
  • A string literal used directly as a numeric expression
  • An empty expression
  • A malformed column or tiebreaker entry

If parseDsl returns without throwing, the configuration is guaranteed to be interpretable: every field, reference, operator arity, and enum token has been checked. The remaining concerns are semantic rather than structural — the parser does not verify that dependencies are acyclic or that a calculation produces a finite number. Preview and test the configuration after every advanced edit.

The serializer produces a canonical (config ...) document with quoted names and labels. It always emits both (columns ...) and (tiebreakers ...), even when either list is empty.

Blockly-generated expressions and DSL expressions share the same internal representation. Converting DSL to blocks and back can normalize formatting, numeric spelling, parentheses, and some equivalent reference forms without changing the calculated value.

Use the preview before saving. It lets you confirm that the current rules produce the intended order without changing published results.

Check that:

  • A team with a better value in the first statistic ranks higher.
  • The second statistic is used only when the first statistic is tied.
  • Higher and lower values are sorted in the correct direction.
  • Averages and percentages behave correctly when pairings have different numbers of scorers.
  • Presider selections are counted only when a presider submitted a tiebreaker choice.
  • The final column labels are clear.

Try preview data that creates ties at several levels. This makes it easier to verify each rule in the sequence.

When the preview matches your tournament’s rules, save the configuration. Then open Standings and verify the column names and team order using the rounds you want to include.

If ballots have already been submitted, changing the configuration can change the displayed standings. Review the updated results before publishing them.

Check the editor for an error message. In the DSL editor, look for unmatched parentheses, missing values, duplicate statistic identifiers, or a tiebreaker that refers to an undefined statistic.

Review whether the statistic should rank from highest to lowest or lowest to highest. Most totals rank higher values first, while statistics such as points allowed may rank lower values first.

Confirm which rounds are selected in the standings and whether every pairing has the expected number of submitted ballots. For averages and percentages, verify that the calculation uses the correct divisor.

Add another statistic to the end of the tiebreaker order if your tournament rules provide an additional method for resolving ties. If the rules permit an unresolved tie, no additional configuration is necessary.

Confirm that a presider was assigned and that the presider submitted a team selection for the pairing. A presider-based statistic cannot count a selection that was never recorded.

Before the tournament begins:

  1. Compare the configuration with the official tournament rules.
  2. Preview at least one tie at each level of the tiebreaker order.
  3. Have another organizer independently review the order and calculations.
  4. Save the configuration before scorers begin submitting ballots.
  5. Avoid changing the rules after results have been published unless the tournament rules require a correction.

This review is especially important because the tiebreaker configuration determines the order shown in published standings.