14 plans in 6 waves covering all 68 requirements for the Tournament Engine phase. Includes research (go-libsql, NATS JetStream, Svelte 5 runes, ICM complexity), plan verification (2 iterations), and user feedback (hand-for-hand UX, SEAT-06 reword, re-entry semantics, integration test, DKK defaults, JWT 7-day expiry, clock tap safety). Wave structure: 1: A (scaffold), B (schema) 2: C (auth/audit), D (clock), E (templates), J (frontend scaffold) 3: F (financial), H (seating), M (layout shell) 4: G (player management) 5: I (tournament lifecycle) 6: K (overview/financials), L (players), N (tables/more) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
Plan E: Blind Structure + Chip Sets + Templates
wave: 2 depends_on: [01-PLAN-A, 01-PLAN-B] files_modified:
- internal/blind/structure.go
- internal/blind/wizard.go
- internal/blind/templates.go
- internal/template/chipset.go
- internal/template/payout.go
- internal/template/buyin.go
- internal/template/tournament.go
- internal/server/routes/templates.go
- internal/blind/wizard_test.go
- internal/template/tournament_test.go autonomous: true requirements: [BLIND-01, BLIND-02, BLIND-03, BLIND-04, BLIND-05, BLIND-06, CHIP-01, CHIP-02, CHIP-03, CHIP-04, FIN-01, FIN-02, FIN-05, FIN-06, FIN-10]
Goal
All reusable building blocks — chip sets, blind structures, payout structures, buy-in configs — have full CRUD with API endpoints. Built-in templates (Turbo, Standard, Deep Stack, WSOP-style) ship as seed data. The structure wizard generates a blind structure from inputs (player count, starting chips, duration, denominations). Tournament templates compose these building blocks. Template management is a dedicated area.
Context
- Templates are compositions of building blocks — not monolithic configs (CONTEXT.md locked decision)
- Building blocks are venue-level — shared across tournaments
- Local changes by default — tournament gets a copy, edits don't affect the template
- Structure wizard lives in template management (CONTEXT.md locked decision)
- Built-in templates ship with the app (BLIND-05)
- Big Blind Ante support alongside standard ante (BLIND-02)
- Mixed game rotation (HORSE, 8-Game) via game type per level (BLIND-03)
- See 01-RESEARCH.md for blind wizard algorithm description
User Decisions (from CONTEXT.md)
- Template-first creation — TD picks a template, everything pre-fills, tweak for tonight, Start
- Building blocks feel like LEGO — pick chip set, pick blind structure, pick payout table, name it, done
- Dedicated template management area — create from scratch, duplicate/edit existing, save tournament config as new template
- Entry count = unique entries only — for payout bracket selection
- Prize rounding — round down to nearest venue-configured denomination
Tasks
**1. Chip Set Service** (`internal/template/chipset.go`): - `ChipSet` struct: ID, Name, IsBuiltin, CreatedAt, UpdatedAt - `ChipDenomination` struct: ID, ChipSetID, Value (int64 cents), ColorHex, Label, SortOrder - CRUD operations: - `CreateChipSet(ctx, name string, denominations []ChipDenomination) (*ChipSet, error)` - `GetChipSet(ctx, id string) (*ChipSet, error)` — includes denominations - `ListChipSets(ctx) ([]ChipSet, error)` - `UpdateChipSet(ctx, id, name string, denominations []ChipDenomination) error` - `DeleteChipSet(ctx, id string) error` — fail if referenced by active tournament - `DuplicateChipSet(ctx, id string, newName string) (*ChipSet, error)` - Built-in chip sets cannot be deleted (is_builtin = true), but can be duplicated2. Blind Structure Service (internal/blind/structure.go):
BlindStructurestruct: ID, Name, IsBuiltin, GameTypeDefault, Notes, CreatedAt, UpdatedAtBlindLevelstruct per BLIND-01:- Position, LevelType (round/break), GameType
- SmallBlind, BigBlind, Ante, BBAnte (all int64) — BLIND-02
- DurationSeconds, ChipUpDenominationValue (*int64 nullable) — CHIP-02
- Notes
- CRUD operations:
CreateStructure(ctx, name string, levels []BlindLevel) (*BlindStructure, error)GetStructure(ctx, id string) (*BlindStructure, error)— includes levels ordered by positionListStructures(ctx) ([]BlindStructure, error)UpdateStructure(ctx, id string, name string, levels []BlindLevel) errorDeleteStructure(ctx, id string) errorDuplicateStructure(ctx, id string, newName string) (*BlindStructure, error)
- Validation:
- At least one round level required
- Small blind < big blind
- Duration > 0 for all levels
- Positions must be contiguous starting from 0
3. Payout Structure Service (internal/template/payout.go):
PayoutStructurestruct: ID, Name, IsBuiltin, CreatedAt, UpdatedAtPayoutBracketstruct: ID, StructureID, MinEntries, MaxEntriesPayoutTierstruct: ID, BracketID, Position, PercentageBasisPoints (int64, 5000 = 50.00%)- CRUD with brackets and tiers as nested entities
- Validation:
- Brackets must cover contiguous ranges (no gaps)
- Tier percentages per bracket must sum to exactly 10000 (100.00%)
- At least one bracket required
4. Buy-in Config Service (internal/template/buyin.go):
BuyinConfigstruct with all fields from schema: buyin amount, starting chips, rake, bounty, rebuy config, addon config, reentry config, late reg configRakeSplitstruct: Category (house/staff/league/season_reserve), Amount (int64 cents)- CRUD operations
- Validation:
- Rake splits sum must equal rake_total
- All amounts must be non-negative
- Rebuy/addon limits must be non-negative
- If bounty_amount > 0, bounty_chip must be > 0
5. API Routes (internal/server/routes/templates.go):
All routes require auth. Create/update/delete require admin role. List/get are floor+ accessible.
Chip Sets:
GET /api/v1/chip-sets— list allGET /api/v1/chip-sets/{id}— get with denominationsPOST /api/v1/chip-sets— createPUT /api/v1/chip-sets/{id}— updateDELETE /api/v1/chip-sets/{id}— deletePOST /api/v1/chip-sets/{id}/duplicate— duplicate
Blind Structures:
GET /api/v1/blind-structures— list allGET /api/v1/blind-structures/{id}— get with levelsPOST /api/v1/blind-structures— createPUT /api/v1/blind-structures/{id}— updateDELETE /api/v1/blind-structures/{id}— deletePOST /api/v1/blind-structures/{id}/duplicate— duplicate
Payout Structures:
GET /api/v1/payout-structures— list allGET /api/v1/payout-structures/{id}— get with brackets and tiersPOST /api/v1/payout-structures— createPUT /api/v1/payout-structures/{id}— updateDELETE /api/v1/payout-structures/{id}— deletePOST /api/v1/payout-structures/{id}/duplicate— duplicate
Buy-in Configs:
GET /api/v1/buyin-configs— list allGET /api/v1/buyin-configs/{id}— getPOST /api/v1/buyin-configs— createPUT /api/v1/buyin-configs/{id}— updateDELETE /api/v1/buyin-configs/{id}— deletePOST /api/v1/buyin-configs/{id}/duplicate— duplicate
All mutations record audit entries.
Verification:
- Full CRUD cycle for each building block type via curl
- Validation rejects invalid inputs (e.g., payout tiers not summing to 100%)
- Built-in items cannot be deleted
- Duplicate creates independent copy
- API Routes:
GET /api/v1/tournament-templates— list allGET /api/v1/tournament-templates/{id}— get with building block summariesGET /api/v1/tournament-templates/{id}/expanded— get with full building block dataPOST /api/v1/tournament-templates— createPUT /api/v1/tournament-templates/{id}— updateDELETE /api/v1/tournament-templates/{id}— deletePOST /api/v1/tournament-templates/{id}/duplicate— duplicate
2. Built-in Seed Data (internal/blind/templates.go):
Create built-in templates that ship with the app. Add to seed migration or boot logic (skip if already exist).
Built-in Blind Structures:
- Turbo (~2hr for 20 players): 15-minute levels, aggressive blind jumps, 1 break
- Levels: 25/50, 50/100, 75/150, 100/200, break, 150/300, 200/400, 300/600, 400/800, break, 600/1200, 800/1600, 1000/2000, 1500/3000, 2000/4000
- Starting chips: 10,000
- Standard (~3-4hr for 20 players): 20-minute levels, moderate progression, 2 breaks
- Levels: 25/50, 50/100, 75/150, 100/200, 150/300, break, 200/400, 300/600, 400/800, 500/1000, break, 600/1200, 800/1600, 1000/2000, 1500/3000, 2000/4000, 3000/6000
- Starting chips: 15,000
- Deep Stack (~5-6hr for 20 players): 30-minute levels, slow progression, 3 breaks
- Starting chips: 25,000, wider level range
- WSOP-style: 60-minute levels, with antes starting at level 4, BB ante option
- Starting chips: 50,000, slow progression
Built-in Payout Structures:
- Standard: 8-20 entries (3 prizes: 50/30/20), 21-30 (4 prizes: 45/26/17/12), 31-40 (5 prizes), 41+ (6 prizes)
Built-in Tournament Templates (compose the above):
- Turbo template (Turbo blinds + Standard payout + default chip set + basic buy-in)
- Standard template
- Deep Stack template
- WSOP-style template
Each built-in has is_builtin = true — cannot be deleted, but can be duplicated.
3. Structure Wizard (internal/blind/wizard.go):
Algorithm to generate a blind structure from inputs:
-
Inputs:
playerCount int,startingChips int64,targetDurationMinutes int,chipSetID string(for denomination alignment) -
Algorithm (from 01-RESEARCH.md):
- Calculate target number of levels:
targetDuration / levelDuration(default 20-minute levels) - Calculate target final big blind:
startingChips * playerCount / 10(roughly — at the end, average stack = 10 BB) - Calculate geometric progression ratio:
(finalBB / initialBB)^(1/numLevels) - Generate levels with geometric blind progression
- Snap each blind to nearest chip denomination from the chip set
- Ensure SB = BB/2 (or closest denomination)
- Add antes starting at ~level 4-5 (standard is ante = BB at higher levels)
- Insert breaks every 4-5 levels (10-minute breaks)
- Mark chip-up breaks when lower denominations are no longer needed
- Calculate target number of levels:
-
Output:
[]BlindLevelready to save as a blind structure -
API Route:
POST /api/v1/blind-structures/wizard— body:{"player_count": 20, "starting_chips": 15000, "target_duration_minutes": 240, "chip_set_id": "..."}- Response: generated
[]BlindLevel(NOT saved — preview only, TD can then save)
4. Tests:
internal/blind/wizard_test.go:- Test wizard generates sensible structure for various inputs (10, 20, 40, 80 players)
- Test blind values align with chip denominations
- Test breaks are inserted at reasonable intervals
- Test generated structure has increasing blinds
- Test edge case: very short tournament (1 hour), very long tournament (8 hours)
internal/template/tournament_test.go:- Test template creation with valid FK references
- Test template creation with invalid FK reference returns error
- Test SaveAsTemplate from running tournament
- Test GetTemplateExpanded returns all building block data
Verification:
- All 4 built-in templates exist after first startup
- Wizard generates a blind structure from sample inputs
- Generated blind values align with chip denominations
- Full CRUD for tournament templates works
- Template expanded endpoint returns complete building block data
Verification Criteria
- Unlimited configurable levels with all fields (round/break, game type, SB/BB, ante, BB ante, duration, chip-up, notes)
- Big Blind Ante field exists alongside standard ante
- Mixed game rotation via game_type per level
- Blind structures can be saved/loaded as reusable templates
- 4 built-in blind structures + 4 built-in tournament templates exist on first boot
- Structure wizard produces a playable structure from inputs
- Chip sets with denominations, colors, and values fully manageable
- Chip-up tracking via chip_up_denomination field per level
- Payout structures with entry-count brackets work correctly
- All building blocks compose into tournament templates
Must-Haves (Goal-Backward)
- Building blocks are independent reusable entities (not embedded in templates)
- Templates compose building blocks by reference (LEGO pattern)
- Built-in templates ship with the app and cannot be deleted
- Structure wizard generates playable blind structures from inputs
- Big Blind Ante and mixed game rotation are supported in the level definition
- Payout structure tiers always sum to exactly 100% per bracket
- Chip denominations have colors for display rendering