From 347c25e8ed974ace71b90690b8ba57f4310dbc7d Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Fri, 20 Feb 2026 22:03:11 +0100 Subject: [PATCH] Add boss team match playstyle rule When enabled, the sticky boss banner shows the next boss's team size as a hint for players who voluntarily match the boss's party count. Handles variant boss teams by using the auto-detected starter variant. Co-Authored-By: Claude Opus 4.6 --- ...-tracker-fv7w--add-team-size-limit-rule.md | 30 ++++++++----------- ...ocke-tracker-sij8--add-gift-clause-rule.md | 4 +-- backend/src/app/seeds/inject_test_data.py | 1 + frontend/src/pages/RunEncounters.tsx | 21 ++++++++++++- frontend/src/types/rules.ts | 9 ++++++ 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md b/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md index 2656421..aeb81eb 100644 --- a/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md +++ b/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md @@ -1,33 +1,29 @@ --- # nuzlocke-tracker-fv7w -title: Add team size limit rule -status: todo +title: Add boss team match rule +status: in-progress type: feature priority: normal created_at: 2026-02-20T19:56:22Z -updated_at: 2026-02-20T20:01:53Z +updated_at: 2026-02-20T21:01:36Z parent: nuzlocke-tracker-49xj --- -Cap the active party size with warnings when the limit is exceeded. +When enabled, hint to the player that they should limit their active party to the same number of Pokemon as the next boss fight. This is a self-imposed difficulty rule — the tracker cannot enforce it since it doesn't track the active party, but it can surface the information. -## Design Decisions +## Design -**Configurable limit:** Add `teamSizeLimit: number | null` to `NuzlockeRules`. `null` means no limit (disabled). Default Pokemon party size is 6, but variants like "trio-locke" use 3. +**Rule:** Add `bossTeamMatch: boolean` to `NuzlockeRules` (default: `false`, category: `playstyle`). -**What counts:** "Active team" = encounters with status `caught` and not fainted. The tracker already tracks this — alive Pokemon are shown in the team section on RunEncounters. +**Display:** When enabled and the sticky boss banner is shown, add a hint next to the boss name showing their team size, e.g. "Next: Brock (2 Pokemon — match their team)". This reuses the existing `nextBoss` and its `pokemon` array. -**No PC box tracking:** The tracker doesn't model a PC box. Excess catches beyond the team limit are still logged normally. The tracker just warns that the team is over capacity. +**Variant bosses:** Some bosses have conditional teams (e.g. rival starter choice). Use the same logic as `BossTeamPreview`: count pokemon without a `conditionLabel` plus those matching the auto-detected variant (via `matchVariant`). Falls back to first variant if no match is detected. -**Enforcement:** Soft enforcement. Show a warning banner on the encounters page when alive count exceeds the limit. Highlight the count in the team section header. Don't block new catches. - -**UI:** Add a numeric input to `RulesConfiguration` (shown when team size toggle is on, min 1, max 6). Display the limit in the sticky bar alongside level caps if enabled. +**Scope:** Frontend-only. No backend or data model changes needed. ## Checklist -- [ ] Add `teamSizeLimit: number | null` to `NuzlockeRules` interface (default: `null`) -- [ ] Add `RuleDefinition` entry under `'difficulty'` category -- [ ] Add numeric input to `RulesConfiguration` (shown when enabled, min 1, max 6) -- [ ] Show warning banner on RunEncounters when alive team count exceeds limit -- [ ] Display team size limit in sticky bar alongside level caps -- [ ] Show count in team section header (e.g., "Team (4/3)" in red when over) \ No newline at end of file +- [x] Add `bossTeamMatch: boolean` to `NuzlockeRules` interface and `DEFAULT_RULES` (default: false) +- [x] Add `RuleDefinition` entry (category: `playstyle`) +- [x] Show boss team size hint in the sticky level cap banner when the rule is enabled +- [x] Handle variant boss teams (use auto-matched variant count when available) \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md b/.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md index 4c13503..418af54 100644 --- a/.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md +++ b/.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-sij8 title: Add gift clause rule -status: in-progress +status: completed type: feature priority: normal created_at: 2026-02-20T19:56:10Z -updated_at: 2026-02-20T20:53:15Z +updated_at: 2026-02-20T20:55:23Z parent: nuzlocke-tracker-49xj --- diff --git a/backend/src/app/seeds/inject_test_data.py b/backend/src/app/seeds/inject_test_data.py index 07c3f46..25c1693 100644 --- a/backend/src/app/seeds/inject_test_data.py +++ b/backend/src/app/seeds/inject_test_data.py @@ -149,6 +149,7 @@ DEFAULT_RULES = { "levelCaps": False, "hardcoreMode": False, "setModeOnly": False, + "bossTeamMatch": False, "egglocke": False, "wonderlocke": False, "randomizer": False, diff --git a/frontend/src/pages/RunEncounters.tsx b/frontend/src/pages/RunEncounters.tsx index 86daa3c..3068893 100644 --- a/frontend/src/pages/RunEncounters.tsx +++ b/frontend/src/pages/RunEncounters.tsx @@ -178,6 +178,17 @@ function matchVariant(labels: string[], starterName?: string | null): string | n return matches.length === 1 ? (matches[0] ?? null) : null } +/** Count boss pokemon for the effective variant (or all if no variants). */ +function getBossTeamSize(pokemon: BossPokemon[], starterName?: string | null): number { + const labels = [ + ...new Set(pokemon.filter((bp) => bp.conditionLabel).map((bp) => bp.conditionLabel!)), + ] + if (labels.length === 0) return pokemon.length + const matched = matchVariant(labels, starterName) + const variant = matched ?? labels[0] ?? null + return pokemon.filter((bp) => bp.conditionLabel === variant || bp.conditionLabel === null).length +} + function BossTeamPreview({ pokemon, starterName, @@ -1060,7 +1071,15 @@ export function RunEncounters() { Level Cap: {currentLevelCap ?? '—'} {nextBoss && ( - Next: {nextBoss.name} + + Next: {nextBoss.name} + {run.rules?.bossTeamMatch && ( + + {' '} + ({getBossTeamSize(nextBoss.pokemon, starterName)} Pokémon — match their team) + + )} + )} {!nextBoss && ( All bosses defeated! diff --git a/frontend/src/types/rules.ts b/frontend/src/types/rules.ts index 84e5c96..bee3cd8 100644 --- a/frontend/src/types/rules.ts +++ b/frontend/src/types/rules.ts @@ -9,6 +9,7 @@ export interface NuzlockeRules { // Playstyle (informational, for stats/categorization) hardcoreMode: boolean setModeOnly: boolean + bossTeamMatch: boolean // Variant (changes which Pokemon can appear) egglocke: boolean @@ -27,6 +28,7 @@ export const DEFAULT_RULES: NuzlockeRules = { // Playstyle - off by default hardcoreMode: false, setModeOnly: false, + bossTeamMatch: false, // Variant - off by default egglocke: false, @@ -93,6 +95,13 @@ export const RULE_DEFINITIONS: RuleDefinition[] = [ 'The game must be played in "Set" battle style, meaning you cannot switch Pokémon after knocking out an opponent.', category: 'playstyle', }, + { + key: 'bossTeamMatch', + name: 'Boss Team Match', + description: + 'Limit your active party to the same number of Pokémon as the boss you are challenging.', + category: 'playstyle', + }, // Variant {