Skip to content

Stage 6B — Knockout (Bracket) Engine #10

Description

@KineticTactic

Starts after: Stage 6A complete.

What this is: The bracket logic for knockout tournaments. Each bracket match is a standard Room (from Stage 3 or 4). This stage wires Rooms into a bracket structure with automatic advancement.

Tasks

  1. Bracket generation

    • POST /api/contests/:id/bracket/generate (admin action, triggered once when registration closes).
    • Auto-round participant count to next power of 2.
    • Insert byes for unfilled slots (auto-walkover wins).
    • Seeding: by CF rating (default) or admin manual drag-and-drop.
    • Standard bracket: highest seed vs lowest seed in round 1.
    • Generate ContestRound documents (QF, SF, F, etc.) and stub ContestRoom documents for all bracket positions.
    • Populate contest:<contestId>:rooms Set with all stub room IDs.
    • Set contest:<contestId>:meta Hash: { format: 'knockout', currentRound: 1, status: 'active' }.
  2. Bracket advancement

    • Winner of each bracket room auto-advances to the next round.
    • When both feeder rooms in a bracket position complete: update the next round's ContestRoom.teams with the two winners.
    • Publish contest.bracket_update to events:contest:<contestId> after each room completes: full bracket state snapshot.
    • ZADD contest:<contestId>:standings <wins> <teamId> after each room.
  3. Round completion detection (race-safe)

    • After every bracket room completes: check if all rooms in the current ContestRound are done.
    • If yes: increment contest:<contestId>:meta.currentRound, publish contest.round_complete to events:contest:<contestId>: { roundNumber, advancingTeams: teamId[] }.
    • This check must be race-safe — two rooms finishing simultaneously must not trigger double-advancement. Implement with a Redis Lua script or a SETNX-based sentinel.
  4. Manual walkover

    • Admin can manually advance a participant: POST /api/contests/rooms/:id/walkover with a required note.
    • Note stored in the audit log. The walkover triggers the same advancement path as a completed room.
  5. Third-place playoff

    • Optional, off by default. Admin toggles at contest setup in Stage 6A.
    • If enabled: two semifinal losers are seeded into a standard bracket room. ContestRoom.bracketPosition = '3PO'.
  6. Bracket state data model

    • Define the bracket snapshot structure carried in contest.bracket_update:
      type BracketNode = {
        roomId: string;
        teams: [teamId: string, teamId: string] | null;
        status: 'pending' | 'active' | 'completed';
        winner: string | null;
        bracketPosition: string;
      }
    • Commit this type definition and document the full snapshot shape in SSE_EVENTS.md.

Testing Gate

  • 4-player knockout: Bracket generates correctly (SF1: seed1 vs seed4, SF2: seed2 vs seed3). Both SF rooms complete → correct finalists advance → winner declared. Contest.status = 'completed', Contest.winner populated.
  • Bye handling: 6 players → bracket rounds to 8. Two participants receive walkover wins in round 1. Correct 6 actual participants appear in round 2.
  • Bracket update events: After each room completes, contest.bracket_update carries correct updated bracket state. Verified at each round.
  • Race condition: Two rooms finish simultaneously in the same round. contest.round_complete fires exactly once.
  • Third-place playoff: Enable it. Semifinal losers play. ContestRoom.bracketPosition = '3PO' in MongoDB.

Handoff Contract

  • All testing gate items pass.
  • The bracket advancement hook is documented — Stage 6C's reconciliation worker calls it.
  • contest.bracket_update and BracketNode type are committed to SSE_EVENTS.md.

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions