Files
skills/docs/gaming/games/game-server/src/enhanced/tournament/tournament-manager/tournament-bracket.internal.md
2026-05-29 08:20:56 +01:00

2.9 KiB

tournament-bracket.internal

Purpose

Pure bracket-generation helpers used by TournamentManager. Extracted to a sibling internal module to keep tournament-manager.ts under the project's 300-line hard limit while preserving full coverage of the generation paths through the manager's public surface.

The helpers are deliberately framework-free — they accept an injectable createMatch factory so the manager retains exclusive ownership of match ids, OTel counters, and other side-effecting machinery, leaving these helpers as deterministic pure functions over the participant + match type domain.

Features

  • roundName(round, total) — human-readable round labels.
  • shuffleWithRng(arr, rng) — in-place Fisher-Yates via injectable Rng.
  • padToPowerOfTwo(participants) — null-padding for single-elim.
  • buildSingleElim(...) — bracket with first-round byes.
  • buildRoundRobin(...) — circle-method scheduling.

Usage: Basic

import { buildSingleElim } from "./tournament-bracket.internal.js";
const rounds = buildSingleElim(participants, (round, position) =>
    createMatch(round, position)
);

Usage: Advanced

import {
    buildRoundRobin,
    shuffleWithRng,
} from "./tournament-bracket.internal.js";

shuffleWithRng(participants, rng);
const rounds = buildRoundRobin(participants, (round, position) => ({
    id: nextId(),
    round,
    position,
    participant1: null,
    participant2: null,
    status: "pending",
    result: null,
    nextMatchId: null,
}));

API Reference

  • roundName(round: number, total: number): string
  • shuffleWithRng<T>(arr: T[], rng: Rng | undefined): void
  • padToPowerOfTwo<T>(participants: Participant<T>[]): (Participant<T> | null)[]
  • buildSingleElim<TP, TR>(participants, createMatch): Round<TP, TR>[]
  • buildRoundRobin<TP, TR>(participants, createMatch): Round<TP, TR>[]

Implementation Notes

buildSingleElim throws INVALID_BRACKET_SIZE if the padded size is not a power of two — this should be unreachable in practice since padToPowerOfTwo always pads to a power of two, but the guard catches caller misuse (e.g. handing in a pre-padded list) without silent corruption of the bracket shape.

buildRoundRobin uses the standard circle method: one participant is fixed and the others rotate around it. For an odd participant count a null slot is injected so each round contains exactly floor(n/2) matches; pairs with null are filtered out before emitting matches so they don't appear in the bracket. This yields n - 1 rounds for n even and n rounds for n odd, matching the conventional schedule.

Shuffle is unbiased Fisher-Yates and uses Rng.nextIntInRange — implementations of Rng that do not provide it (it is optional in the core contract) leave the order untouched, which is the conservative default for any tournament harness that prefers explicit error over silent non-determinism.