Release: test infrastructure, rules overhaul, and design refresh #30
@@ -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
|
||||
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)*
|
||||
@@ -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
|
||||
@@ -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
|
||||
33
.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md
Normal file
33
.beans/nuzlocke-tracker-fv7w--add-team-size-limit-rule.md
Normal file
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
18
.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md
Normal file
18
.beans/nuzlocke-tracker-sij8--add-gift-clause-rule.md
Normal file
@@ -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"
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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({
|
||||
|
||||
<div className="bg-surface-1 rounded-lg shadow">
|
||||
<div className="px-4 py-3 border-b border-border-default">
|
||||
<h3 className="text-lg font-medium text-text-primary">Difficulty Modifiers</h3>
|
||||
<p className="text-sm text-text-tertiary">Optional rules to increase the challenge</p>
|
||||
<h3 className="text-lg font-medium text-text-primary">Playstyle</h3>
|
||||
<p className="text-sm text-text-tertiary">
|
||||
Describe how you're playing — doesn't affect tracker behavior
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-4">
|
||||
{difficultyRules.map((rule) => (
|
||||
{playstyleRules.map((rule) => (
|
||||
<RuleToggle
|
||||
key={rule.key}
|
||||
name={rule.name}
|
||||
@@ -89,26 +90,6 @@ export function RulesConfiguration({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{completionRules.length > 0 && (
|
||||
<div className="bg-surface-1 rounded-lg shadow">
|
||||
<div className="px-4 py-3 border-b border-border-default">
|
||||
<h3 className="text-lg font-medium text-text-primary">Completion</h3>
|
||||
<p className="text-sm text-text-tertiary">When is the run considered complete</p>
|
||||
</div>
|
||||
<div className="px-4">
|
||||
{completionRules.map((rule) => (
|
||||
<RuleToggle
|
||||
key={rule.key}
|
||||
name={rule.name}
|
||||
description={rule.description}
|
||||
enabled={rules[rule.key]}
|
||||
onChange={(value) => handleRuleChange(rule.key, value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user