From e25d1cf24cc163fcc25b14f831c2f2f36b43f789 Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Fri, 20 Feb 2026 21:20:23 +0100 Subject: [PATCH] Remove unused nuzlocke rules, reorganize into core and playstyle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove firstEncounterOnly, permadeath, nicknameRequired, and postGameCompletion from the rules system — they are either implicit (it's a nuzlocke tracker) or not enforced. Move levelCaps to core (it's displayed in the sticky bar). Create a new "playstyle" category for hardcoreMode and setModeOnly — informational rules useful for stats but not enforced by the tracker. Remove the completion category entirely. Add sub-task beans for the rules overhaul epic. Co-Authored-By: Claude Opus 4.6 --- ...er-49xj--overhaul-nuzlocke-rules-system.md | 45 +++-------- ...y--add-type-restriction-rules-monolocke.md | 33 ++++++++ ...glocke-wonderlocke-and-randomizer-rules.md | 22 ++++++ ...-tracker-fv7w--add-team-size-limit-rule.md | 33 ++++++++ ...r-knnc--add-staticlegendary-clause-rule.md | 32 ++++++++ ...cker-o7r8--remove-unused-nuzlocke-rules.md | 26 ++++++ ...ocke-tracker-sij8--add-gift-clause-rule.md | 18 +++++ backend/src/app/seeds/inject_test_data.py | 5 +- frontend/src/components/RuleBadges.tsx | 4 +- .../src/components/RulesConfiguration.tsx | 31 ++------ frontend/src/types/rules.ts | 79 +++++-------------- 11 files changed, 204 insertions(+), 124 deletions(-) create mode 100644 .beans/nuzlocke-tracker-bs0y--add-type-restriction-rules-monolocke.md create mode 100644 .beans/nuzlocke-tracker-fitk--add-egglocke-wonderlocke-and-randomizer-rules.md create mode 100644 .beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md create mode 100644 .beans/nuzlocke-tracker-knnc--add-staticlegendary-clause-rule.md create mode 100644 .beans/nuzlocke-tracker-o7r8--remove-unused-nuzlocke-rules.md create mode 100644 .beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md diff --git a/.beans/nuzlocke-tracker-49xj--overhaul-nuzlocke-rules-system.md b/.beans/nuzlocke-tracker-49xj--overhaul-nuzlocke-rules-system.md index 84ddef8..f7c8f5f 100644 --- a/.beans/nuzlocke-tracker-49xj--overhaul-nuzlocke-rules-system.md +++ b/.beans/nuzlocke-tracker-49xj--overhaul-nuzlocke-rules-system.md @@ -36,37 +36,18 @@ These are boolean flags with real tracker logic: - `randomizer` — the run uses a randomized ROM. Same behavior: encounter Pokemon selection allows picking from ALL Pokemon since the dex is randomized. - `giftClause` — in-game gift Pokemon are free and do not count against the area's encounter limit. When enabled, gift-origin encounters should bypass the route-lock check (similar to how shinyClause bypasses it for shinies). -### Follow-up beans to CREATE -Rules that need more complex logic, tracked separately: -- Type Restrictions (Monolocke) — restrict team to specific types -- Team Size Limit — cap active party size with warnings -- Static/Legendary Clause — whether static encounters count or are banned +### Complex rules (need design work) +These need more complex logic and are tracked as draft sub-tasks: +- Type Restrictions (Monolocke) — bs0y +- Team Size Limit — fv7w +- Static/Legendary Clause — knnc -## Checklist +## Children -### Cleanup: remove unused rules -- [ ] Remove `firstEncounterOnly`, `permadeath`, `nicknameRequired`, `setModeOnly`, `postGameCompletion` from `NuzlockeRules` interface and `DEFAULT_RULES` -- [ ] Remove their entries from `RULE_DEFINITIONS` - -### Add new rules: frontend types -- [ ] Add `egglocke`, `wonderlocke`, `randomizer`, `giftClause` to `NuzlockeRules` interface and `DEFAULT_RULES` (default: false) -- [ ] Add `RuleDefinition` entries for the new rules with appropriate categories - -### Add new rules: egglocke / wonderlocke / randomizer logic -- [ ] When any of `egglocke`, `wonderlocke`, or `randomizer` is enabled, the encounter Pokemon selector should allow picking from ALL Pokemon (not just the game's regional dex) -- [ ] Reuse the existing "all Pokemon" selector pattern used in admin panel encounter creation and boss team creation - -### Add new rules: giftClause logic -- [ ] When `giftClause` is enabled, gift-origin encounters should bypass the route-lock check in the backend (similar to shinyClause bypass) -- [ ] Update the encounter creation endpoint to check for giftClause when origin is "gift" - -### Update components and pages -- [ ] Update `RulesConfiguration`, `RuleToggle`, and `RuleBadges` components as needed -- [ ] Update `NewRun.tsx` and `NewGenlocke.tsx` if they reference removed rules - -### Backend and data -- [ ] Verify backend encounter logic still works for removed rules (uses `.get()` with defaults) -- [ ] Update backend test seed data if it references removed rules - -### Follow-ups -- [ ] Create follow-up beans for: Type Restrictions, Team Size Limit, Static/Legendary Clause \ No newline at end of file +Work is tracked in sub-tasks: +- **o7r8** — Remove unused nuzlocke rules +- **fitk** — Add egglocke, wonderlocke, and randomizer rules +- **sij8** — Add gift clause rule +- **bs0y** — Add type restriction rules (monolocke) *(draft)* +- **fv7w** — Add team size limit rule *(draft)* +- **knnc** — Add static/legendary clause rule *(draft)* \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-bs0y--add-type-restriction-rules-monolocke.md b/.beans/nuzlocke-tracker-bs0y--add-type-restriction-rules-monolocke.md new file mode 100644 index 0000000..1377036 --- /dev/null +++ b/.beans/nuzlocke-tracker-bs0y--add-type-restriction-rules-monolocke.md @@ -0,0 +1,33 @@ +--- +# nuzlocke-tracker-bs0y +title: Add type restriction rules (monolocke) +status: todo +type: feature +priority: normal +created_at: 2026-02-20T19:56:16Z +updated_at: 2026-02-20T20:01:40Z +parent: nuzlocke-tracker-49xj +--- + +Restrict team composition to specific types (monolocke and similar variants). + +## Design Decisions + +**Type selection:** Multi-select from the 18 standard Pokemon types. A monolocke selects one type; multi-type variants (e.g., "fire and water only") select multiple. + +**Dual-type matching:** A Pokemon qualifies if at least one of its types is in the allowed set. This matches the community standard for monolocke — e.g., in a Fire monolocke, Charizard (Fire/Flying) is allowed because it has Fire. + +**Enforcement:** Soft enforcement via UI warnings, not hard blocks. The tracker warns when a caught Pokemon doesn't match the allowed types but doesn't prevent logging it. Reason: players sometimes need to use HM slaves or have edge cases the tracker shouldn't block. + +**Data model:** Add `allowedTypes: string[]` to `NuzlockeRules`. Empty array means no restriction (disabled). This keeps it in the existing JSONB rules blob on the run. + +**UI:** Add a "Type Restrictions" section to `RulesConfiguration` with a multi-select type picker (reuse the type badge styling from `TypeBadge`). Show a warning badge on encounters that don't match. + +## Checklist + +- [ ] Add `allowedTypes: string[]` to `NuzlockeRules` interface (default: `[]`) +- [ ] Add a new `'variant'` category to `RuleDefinition` for variant rules +- [ ] Add type multi-select UI to `RulesConfiguration` (shown when allowedTypes toggle is on) +- [ ] Show warning indicator on `PokemonCard` and encounter list for Pokemon that don't match allowed types +- [ ] Add `RuleBadge` display for active type restriction (e.g., "Monolocke: Fire") +- [ ] Update `RuleBadges` color mapping for the new `'variant'` category \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-fitk--add-egglocke-wonderlocke-and-randomizer-rules.md b/.beans/nuzlocke-tracker-fitk--add-egglocke-wonderlocke-and-randomizer-rules.md new file mode 100644 index 0000000..eaa1627 --- /dev/null +++ b/.beans/nuzlocke-tracker-fitk--add-egglocke-wonderlocke-and-randomizer-rules.md @@ -0,0 +1,22 @@ +--- +# nuzlocke-tracker-fitk +title: Add egglocke, wonderlocke, and randomizer rules +status: todo +type: feature +created_at: 2026-02-20T19:56:05Z +updated_at: 2026-02-20T19:56:05Z +parent: nuzlocke-tracker-49xj +--- + +Add three new boolean rules that all share the same tracker logic: when enabled, the encounter Pokemon selector allows picking from ALL Pokemon (not just the game's regional dex). + +- `egglocke` — all caught Pokemon are replaced with traded eggs +- `wonderlocke` — all caught Pokemon are Wonder Traded away +- `randomizer` — the run uses a randomized ROM + +## Checklist + +- [ ] Add `egglocke`, `wonderlocke`, `randomizer` to `NuzlockeRules` interface and `DEFAULT_RULES` (default: false) +- [ ] Add `RuleDefinition` entries with appropriate categories +- [ ] When any of these is enabled, encounter Pokemon selector should allow picking from ALL Pokemon +- [ ] Reuse the existing "all Pokemon" selector pattern used in admin panel encounter creation and boss team creation \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md b/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md new file mode 100644 index 0000000..2656421 --- /dev/null +++ b/.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md @@ -0,0 +1,33 @@ +--- +# nuzlocke-tracker-fv7w +title: Add team size limit rule +status: todo +type: feature +priority: normal +created_at: 2026-02-20T19:56:22Z +updated_at: 2026-02-20T20:01:53Z +parent: nuzlocke-tracker-49xj +--- + +Cap the active party size with warnings when the limit is exceeded. + +## Design Decisions + +**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. + +**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. + +**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. + +**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. + +## 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 diff --git a/.beans/nuzlocke-tracker-knnc--add-staticlegendary-clause-rule.md b/.beans/nuzlocke-tracker-knnc--add-staticlegendary-clause-rule.md new file mode 100644 index 0000000..6bfc401 --- /dev/null +++ b/.beans/nuzlocke-tracker-knnc--add-staticlegendary-clause-rule.md @@ -0,0 +1,32 @@ +--- +# nuzlocke-tracker-knnc +title: Add static/legendary clause rule +status: todo +type: feature +priority: normal +created_at: 2026-02-20T19:56:27Z +updated_at: 2026-02-20T20:02:07Z +parent: nuzlocke-tracker-49xj +--- + +Control whether static/legendary encounters count against the area's encounter limit. + +## Design Decisions + +**Scope:** This rule covers overworld Pokemon that are always available (legendaries, Snorlax blocking the road, Sudowoodo, Voltorb in the power plant, etc.). These are distinct from gifts (given by NPCs) which are covered by giftClause (sij8). + +**Encounter method:** The existing encounter method list (walk, surf, gift, fossil, etc.) doesn't have a "static" method. Add `static` as a new encounter method in the seed data and `METHOD_CONFIG`. Static encounters are one-time overworld Pokemon the player walks up to and battles. + +**Rule behavior:** `staticClause: boolean` (default: false). When enabled, encounters with method `static` bypass the route-lock check (same pattern as shinyClause and giftClause). This means static Pokemon are "free" and don't consume the area's encounter. + +**No legendary ban:** Rather than banning legendaries outright, the community standard is to let the player choose. The tracker just needs to support logging static encounters correctly. Players who want to ban legendaries simply don't catch them. + +**Interaction with giftClause:** These are separate rules. `giftClause` covers NPC gifts (method: `gift`). `staticClause` covers overworld statics (method: `static`). A player can enable both, one, or neither. + +## Checklist + +- [ ] Add `static` encounter method to seed data and `METHOD_CONFIG` / `METHOD_ORDER` +- [ ] Add `staticClause` to `NuzlockeRules` interface and `DEFAULT_RULES` (default: false) +- [ ] Add `RuleDefinition` entry under `'core'` category +- [ ] When enabled, encounters with method `static` bypass route-lock check in backend (add to `skip_route_lock` condition alongside shiny/egg/shed/transfer) +- [ ] Update encounter creation frontend to show `static` as a selectable method where appropriate \ No newline at end of file diff --git a/.beans/nuzlocke-tracker-o7r8--remove-unused-nuzlocke-rules.md b/.beans/nuzlocke-tracker-o7r8--remove-unused-nuzlocke-rules.md new file mode 100644 index 0000000..9f03b92 --- /dev/null +++ b/.beans/nuzlocke-tracker-o7r8--remove-unused-nuzlocke-rules.md @@ -0,0 +1,26 @@ +--- +# nuzlocke-tracker-o7r8 +title: Remove unused nuzlocke rules +status: completed +type: feature +priority: normal +created_at: 2026-02-20T19:55:59Z +updated_at: 2026-02-20T20:04:33Z +parent: nuzlocke-tracker-49xj +--- + +Remove 5 rules that either define what a nuzlocke is (always true) or don't affect tracker behavior: +- `firstEncounterOnly` — implicit; it's a nuzlocke tracker +- `permadeath` — implicit; it's a nuzlocke tracker +- `nicknameRequired` — not enforced or tracked +- `setModeOnly` — not enforced or tracked +- `postGameCompletion` — not enforced or tracked + +## Checklist + +- [x] Remove from `NuzlockeRules` interface and `DEFAULT_RULES` +- [x] Remove their entries from `RULE_DEFINITIONS` +- [x] Update `RulesConfiguration`, `RuleToggle`, and `RuleBadges` components as needed +- [x] Update `NewRun.tsx` and `NewGenlocke.tsx` if they reference removed rules +- [x] Verify backend encounter logic still works (uses `.get()` with defaults) +- [x] Update backend test seed data if it references removed rules \ 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 new file mode 100644 index 0000000..2b34da1 --- /dev/null +++ b/.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md @@ -0,0 +1,18 @@ +--- +# nuzlocke-tracker-sij8 +title: Add gift clause rule +status: todo +type: feature +created_at: 2026-02-20T19:56:10Z +updated_at: 2026-02-20T19:56:10Z +parent: nuzlocke-tracker-49xj +--- + +Add a new `giftClause` boolean rule: in-game gift Pokemon are free and do not count against the area's encounter limit. + +## Checklist + +- [ ] Add `giftClause` to `NuzlockeRules` interface and `DEFAULT_RULES` (default: false) +- [ ] Add `RuleDefinition` entry with appropriate category +- [ ] When enabled, gift-origin encounters bypass the route-lock check in the backend (similar to shinyClause bypass) +- [ ] Update the encounter creation endpoint to check for giftClause when origin is "gift" \ No newline at end of file diff --git a/backend/src/app/seeds/inject_test_data.py b/backend/src/app/seeds/inject_test_data.py index 53ccdaa..6b6a017 100644 --- a/backend/src/app/seeds/inject_test_data.py +++ b/backend/src/app/seeds/inject_test_data.py @@ -142,14 +142,11 @@ RUN_DEFS = [ # Default rules (matches frontend DEFAULT_RULES) DEFAULT_RULES = { - "firstEncounterOnly": True, - "permadeath": True, - "nicknameRequired": True, "duplicatesClause": True, "shinyClause": True, "pinwheelClause": True, - "hardcoreMode": False, "levelCaps": False, + "hardcoreMode": False, "setModeOnly": False, } diff --git a/frontend/src/components/RuleBadges.tsx b/frontend/src/components/RuleBadges.tsx index db76e79..b9e8156 100644 --- a/frontend/src/components/RuleBadges.tsx +++ b/frontend/src/components/RuleBadges.tsx @@ -21,9 +21,7 @@ export function RuleBadges({ rules }: RuleBadgesProps) { className={`px-2 py-0.5 rounded-full text-xs font-medium ${ def.category === 'core' ? 'bg-blue-900/40 text-blue-300 light:bg-blue-100 light:text-blue-700' - : def.category === 'completion' - ? 'bg-green-900/40 text-green-300 light:bg-green-100 light:text-green-700' - : 'bg-amber-900/40 text-amber-300 light:bg-amber-100 light:text-amber-800' + : 'bg-purple-900/40 text-purple-300 light:bg-purple-100 light:text-purple-700' }`} > {def.name} diff --git a/frontend/src/components/RulesConfiguration.tsx b/frontend/src/components/RulesConfiguration.tsx index 471cac1..d129c2c 100644 --- a/frontend/src/components/RulesConfiguration.tsx +++ b/frontend/src/components/RulesConfiguration.tsx @@ -19,8 +19,7 @@ export function RulesConfiguration({ ? RULE_DEFINITIONS.filter((r) => !hiddenRules.has(r.key)) : RULE_DEFINITIONS const coreRules = visibleRules.filter((r) => r.category === 'core') - const difficultyRules = visibleRules.filter((r) => r.category === 'difficulty') - const completionRules = visibleRules.filter((r) => r.category === 'completion') + const playstyleRules = visibleRules.filter((r) => r.category === 'playstyle') const handleRuleChange = (key: keyof NuzlockeRules, value: boolean) => { onChange({ ...rules, [key]: value }) @@ -74,11 +73,13 @@ export function RulesConfiguration({
-

Difficulty Modifiers

-

Optional rules to increase the challenge

+

Playstyle

+

+ Describe how you're playing — doesn't affect tracker behavior +

- {difficultyRules.map((rule) => ( + {playstyleRules.map((rule) => (
- - {completionRules.length > 0 && ( -
-
-

Completion

-

When is the run considered complete

-
-
- {completionRules.map((rule) => ( - handleRuleChange(rule.key, value)} - /> - ))} -
-
- )}
) } diff --git a/frontend/src/types/rules.ts b/frontend/src/types/rules.ts index 846e39d..7249599 100644 --- a/frontend/src/types/rules.ts +++ b/frontend/src/types/rules.ts @@ -1,68 +1,36 @@ export interface NuzlockeRules { - // Core rules - firstEncounterOnly: boolean - permadeath: boolean - nicknameRequired: boolean + // Core rules (affect tracker behavior) duplicatesClause: boolean shinyClause: boolean pinwheelClause: boolean - - // Difficulty modifiers - hardcoreMode: boolean levelCaps: boolean - setModeOnly: boolean - // Completion - postGameCompletion: boolean + // Playstyle (informational, for stats/categorization) + hardcoreMode: boolean + setModeOnly: boolean } export const DEFAULT_RULES: NuzlockeRules = { - // Core rules - standard Nuzlocke - firstEncounterOnly: true, - permadeath: true, - nicknameRequired: true, + // Core rules duplicatesClause: true, shinyClause: true, pinwheelClause: true, - - // Difficulty modifiers - off by default - hardcoreMode: false, levelCaps: false, - setModeOnly: false, - // Completion - postGameCompletion: false, + // Playstyle - off by default + hardcoreMode: false, + setModeOnly: false, } export interface RuleDefinition { key: keyof NuzlockeRules name: string description: string - category: 'core' | 'difficulty' | 'completion' + category: 'core' | 'playstyle' } export const RULE_DEFINITIONS: RuleDefinition[] = [ // Core rules - { - key: 'firstEncounterOnly', - name: 'First Encounter Only', - description: - 'You may only catch the first Pokémon encountered in each area. If you fail to catch it, you get nothing from that area.', - category: 'core', - }, - { - key: 'permadeath', - name: 'Permadeath', - description: - 'If a Pokémon faints, it is considered dead and must be released or permanently boxed.', - category: 'core', - }, - { - key: 'nicknameRequired', - name: 'Nickname Required', - description: 'All caught Pokémon must be given a nickname to form a stronger bond.', - category: 'core', - }, { key: 'duplicatesClause', name: 'Duplicates Clause', @@ -84,35 +52,26 @@ export const RULE_DEFINITIONS: RuleDefinition[] = [ 'Sub-zones within a location group each get their own encounter instead of sharing one.', category: 'core', }, - - // Difficulty modifiers - { - key: 'hardcoreMode', - name: 'Hardcore Mode', - description: 'No items may be used during battle. Held items are still allowed.', - category: 'difficulty', - }, { key: 'levelCaps', name: 'Level Caps', description: "Your Pokémon cannot exceed the level of the next Gym Leader's highest-level Pokémon before challenging them.", - category: 'difficulty', + category: 'core', + }, + + // Playstyle + { + key: 'hardcoreMode', + name: 'Hardcore Mode', + description: 'No items may be used during battle. Held items are still allowed.', + category: 'playstyle', }, { key: 'setModeOnly', name: 'Set Mode Only', description: 'The game must be played in "Set" battle style, meaning you cannot switch Pokémon after knocking out an opponent.', - category: 'difficulty', - }, - - // Completion - { - key: 'postGameCompletion', - name: 'Post-Game Completion', - description: - 'The run continues into post-game content instead of ending after the Champion is defeated.', - category: 'completion', + category: 'playstyle', }, ]