diff --git a/TASKS.md b/TASKS.md deleted file mode 100644 index 63e306d..0000000 --- a/TASKS.md +++ /dev/null @@ -1,386 +0,0 @@ -# Rolemaster Support Plan - -## Goal - -Extend the app with a new `rolemaster` ruleset that supports generic standard Rolemaster expressions plus the special open-ended percentile case: - -1. Standard expressions: `NdS + x`, with an implicit dice count of `1` when no digit appears before `d` -2. Open-ended percentile: `d100! + x` with Rolemaster low-end and high-end behavior - -The initial scope is only roll definition, validation, execution, logging, and UI support for generic standard Rolemaster rolls and open-ended percentile rolls. This does not include Rolemaster attack tables, critical tables, resistance rolls, maneuver tables, or character sheet math beyond the explicit roll modifier entered for a skill. - -## Scope Assumptions - -- A Rolemaster campaign chooses the new `rolemaster` ruleset at campaign creation time. -- Rolemaster skills are ruleset-specific and should not reuse D6-only options such as `WildDice` and `AllowFumble`. -- Open-ended percentile behavior is defined as: - - Roll the first `d100`. - - If the first roll is `96` or higher, roll another high-ended `d100` recursively and add it. - - If the first roll is less than or equal to the skill's configured fumble range, roll another high-ended `d100` recursively and subtract it. - - Recursive follow-up rolls are high-ended only. -- Standard Rolemaster rolls do not open-end and do not use a fumble range. -- The modifier `x` should be an integer. -- Negative modifiers are supported for Rolemaster only. -- D6 and D&D 5e keep their current non-negative modifier rules unless separately re-scoped in a future change. -- Existing D6 and D&D 5e behavior must remain unchanged. -- The migration must be validated against a copied temp-file instance of `RpgRoller/App_Data/rpgroller.development.db`, which currently contains a D6 campaign. - -## Clarifications - -- The Rolemaster modifier should allow negative values only for Rolemaster. -- The standard fumble range for open-ended percentile skills is `0-5`, but can be configured individually for each skill. -- The fumble range has to be guaranteed to be less than 96. -- Roll breakdown text format for low-ended subtraction: `12 (03) -97 -44 +15 = -114` -- Skill groups need to support Rolemaster defaults for all of these fields, to stay consistent with current prototype behavior -- Rolemaster uses expression parsing with canonical syntax: - - standard roll with implicit count: `d10` - - standard roll with explicit count: `15d10` - - standard percentile: `d100+4` - - open-ended percentile: `d100!+85` - - negative modifiers are valid only for Rolemaster, for example `d100-15` - - open-ended syntax is only valid for `d100!` - -## Architecture Guardrails - -- Keep minimal API endpoint handlers thin and continue to place gameplay validation and roll execution in services. -- Keep Blazor components focused on form state, rendering, and API orchestration; do not duplicate Rolemaster rules in component code. -- Keep request/response DTOs separate from domain/persistence models. -- Preserve the current lazy log-detail loading flow so Rolemaster detail stays out of hot list payloads. -- Prefer additive changes to contracts and storage over replacing current D6 / D&D shapes outright. - -## Backward Compatibility - -- Existing D6 and D&D 5e API request and response fields must remain valid throughout the Rolemaster rollout. -- Existing persisted skills, skill groups, and roll log rows must continue to load without migration-time data loss. -- Startup migration against the existing development SQLite database shape must succeed without data loss or post-migration behavior regressions for legacy D6 data. -- Any richer die metadata must deserialize safely for old rows that only contain the current `RollDieResult` shape. -- Payload budgets for roster, log page, roll mutation responses, and lazy-loaded roll detail must remain explicitly guarded by tests. - -## Design Direction - -### 1. Add a new ruleset - -Introduce `RulesetKind.Rolemaster` and expose it through `DiceRules` and campaign ruleset APIs. - -Tasks: -- Add `Rolemaster` to `RulesetKind`. -- Update `DiceRules.TryParseRulesetId`. -- Update `DiceRules.ToRulesetId`. -- Add `rolemaster` to `DiceRules.SupportedRulesets`. -- Update tests that enumerate supported rulesets and campaign creation behavior. - -Affected areas: -- `RpgRoller/Domain/GameModels.cs` -- `RpgRoller/Services/DiceRules.cs` -- `RpgRoller/Contracts/ApiContracts.cs` -- related service and API tests - -### 2. Refactor skill definition from "expression + D6 options" to "ruleset-aware roll definition" - -The current model is D6-shaped: -- `DiceRollDefinition` -- `WildDice` -- `AllowFumble` - -That shape is not a clean fit for Rolemaster. The Rolemaster extension should still preserve expression parsing, but it needs a ruleset-aware internal definition so the app can validate, execute, and display Rolemaster rolls safely. - -Recommended model additions: -- Keep existing fields working for D6 and D&D 5e. -- Preserve `DiceRollDefinition` as the authoritative user-facing input for D6, D&D 5e, and Rolemaster. -- Add only the extra Rolemaster fields that cannot be encoded cleanly in the expression itself, specifically the fumble range. -- Centralize parsing and validation into a single ruleset-aware validator that returns a canonical internal representation. - -Tasks: -- Define a ruleset-aware skill definition model in domain/contracts. -- Keep `DiceRollDefinition` as persisted state and define canonical formatting rules for Rolemaster expressions. -- Add mapping helpers so UI and API get a stable, ruleset-aware DTO. -- Ensure skill groups can store the same Rolemaster defaults as skills. - -Affected areas: -- `RpgRoller/Domain/GameModels.cs` -- `RpgRoller/Contracts/ApiContracts.cs` -- `RpgRoller/Services/GameService.cs` -- `RpgRoller/Components/Pages/Home.Models.cs` - -### 3. Add Rolemaster-specific validation - -Current validation is split between generic expression parsing and D6 option validation. Rolemaster needs its own validation layer. - -Validation rules to add: -- standard Rolemaster expressions - - supports generic `NdS` syntax with any supported side count - - assumes a dice count of `1` when the count is omitted before `d` - - integer modifier required - - no fumble range -- `RolemasterOpenEndedPercentile` - - fixed base roll `1d100` - - integer modifier required - - fumble range required and validated - - fumble range must be below `96` -- reject D6-only options for Rolemaster skills -- reject Rolemaster-only fields for D6 / D&D 5e skills -- reject negative modifiers for D6 / D&D 5e expressions -- accept negative modifiers for Rolemaster expressions only - -Tasks: -- Replace or extend `ValidateSkillDefinition`. -- Extend `DiceRules.ParseExpression` with a Rolemaster-focused parser/validator path. -- Add canonical Rolemaster expression parsing rules: - - `d10`, `2d10+48`, and `15d10-15` for generic standard rolls - - `d100+4` and `d100-20` for standard percentile - - `d100!+85` and `d100!-15` for open-ended percentile - - reject open-ended syntax for non-percentile or multi-die expressions such as `2d10!+1` -- Keep Rolemaster fumble range validation separate from the expression because it is configured independently. -- Add canonical display formatting for Rolemaster skills, for example: - - `Rolemaster: d10` - - `Rolemaster: 15d10+15` - - `Open-ended percentile: OE 1d100+48, fumble <= 5` -- Add explicit regression tests that D6 / D&D still reject negative modifiers after Rolemaster support lands. - -Affected areas: -- `RpgRoller/Services/DiceRules.cs` -- `RpgRoller/Services/GameService.cs` -- `RpgRoller.Tests/Services/DiceRulesTests.cs` -- `RpgRoller.Tests/Services/ServiceSkillGroupAndOwnershipTests.cs` -- `RpgRoller.Tests/Services/ServiceSkillRollTests.cs` - -### 4. Implement Rolemaster roll execution - -Add a new Rolemaster execution path alongside the existing D6 and standard roll logic. - -Recommended internal shape: -- `ComputeRoll` dispatches by ruleset and roll kind. -- Add dedicated methods: - - `ComputeRolemasterStandardRoll` - - `ComputeRolemasterOpenEndedRoll` - - helper `RollRolemasterHighOpenEndedChain` - -Open-ended algorithm: -- Roll first `d100`. -- Start total with first roll. -- If first roll `>= 96`: - - roll one or more high-ended follow-up `d100`s - - add all follow-up rolls -- Else if first roll `<= fumble range`: - - roll one or more high-ended follow-up `d100`s - - subtract the follow-up total -- Apply modifier after base/follow-up math. -- Produce a full breakdown string. -- Persist enough die detail to reconstruct the event in log detail and UI. - -Tasks: -- Extend `ComputeRoll` dispatch. -- Add Rolemaster-specific roll methods. -- Parse Rolemaster expressions into an internal roll definition before execution rather than branching on raw strings in multiple places. -- Decide whether the current `RollDieResult` DTO is sufficient. - - likely not sufficient, because it only has `Crit`, `Fumble`, `Wild`, `Removed`, `Added` -- Recommended DTO extension: - - `Sequence` or `Order` - - `Phase` / `Kind` such as `initial`, `open-ended-high`, `open-ended-low-subtract` - - `SignedContribution` or equivalent metadata for subtraction -- Update log summary generation so Rolemaster rolls remain understandable in compact log entries without bloating list payloads. -- Keep lazy detail expansion as the place where the full Rolemaster breakdown and rich die metadata are loaded. - -Affected areas: -- `RpgRoller/Services/GameService.cs` -- `RpgRoller/Contracts/ApiContracts.cs` -- `RpgRoller/Contracts/RpgRollerJsonSerializerContext.cs` -- `RpgRoller/Components/Pages/HomeControls/RollDiceStrip.razor.cs` - -### 5. Update persistence and migrations - -Rolemaster support needs persisted structured fields for skills and skill groups, and possibly richer serialized roll-die details. - -Tasks: -- Update `Skill` and `SkillGroup` EF models. -- Update `RpgRollerDbContext` configuration. -- Add EF Core migration for new Rolemaster fields. -- Preserve existing data for D6 / D&D 5e campaigns. -- Decide migration defaults for legacy rows. -- Update `SqliteSchemaUpgrader` coverage expectations if schema assumptions change. -- Verify serialized roll log payloads remain readable for old entries after DTO extension. -- Ensure additive schema changes allow mixed old/new data during migration and startup upgrade. -- Validate the real startup migration path against a copied temp-file instance of `RpgRoller/App_Data/rpgroller.development.db`. -- Verify that the migrated copy still loads the existing D6 campaign data and that legacy D6 skills remain rollable after startup migration. - -Data migration recommendations: -- Legacy D6 / D&D 5e skills should retain current behavior with deterministic defaults. -- New Rolemaster fields should be nullable or have safe defaults during migration. -- Avoid destructive migration patterns; favor additive schema evolution. -- Never run destructive migration verification against the source development DB file; always copy it first and test the copy. - -Affected areas: -- `RpgRoller/Data/RpgRollerDbContext.cs` -- `RpgRoller/Migrations/*` -- `RpgRoller/Hosting/SqliteSchemaUpgrader.cs` -- `RpgRoller.Tests/HostingCoverageTests.cs` - -### 6. Expose Rolemaster in APIs and frontend models - -The UI currently assumes only `IsD6` versus "not D6". That binary split will not scale to Rolemaster. - -Tasks: -- Replace `IsD6` branching with a ruleset-aware UI model. -- Extend skill and skill-group request/response contracts with Rolemaster fields. -- Ensure campaign creation UI includes `Rolemaster` in the ruleset picker. -- Update workspace models and display helpers so Rolemaster skill summaries render correctly. - -Recommended frontend model changes: -- Introduce ruleset-aware form state rather than raw D6 toggles. -- Keep "Expression" as the primary input, and provide Rolemaster-specific help text, examples, and validation when the selected campaign ruleset is `rolemaster`. -- Show fumble range input only when the current Rolemaster expression is an open-ended percentile roll. -- Use progressive disclosure so only the fields relevant to the current Rolemaster expression are shown. -- Add inline validation on blur and submit, visible required indicators, and `aria-live` / alert semantics for validation errors. -- Preserve the existing app visual language instead of introducing a separate Rolemaster-specific theme. - -Affected areas: -- `RpgRoller/Contracts/ApiContracts.cs` -- `RpgRoller/Components/Pages/Home.Models.cs` -- `RpgRoller/Components/Pages/HomeControls/SkillFormModal.razor` -- `RpgRoller/Components/Pages/HomeControls/SkillFormModal.razor.cs` -- `RpgRoller/Components/Pages/HomeControls/CharacterPanel.razor` -- `RpgRoller/Components/Pages/HomeControls/CharacterPanel.razor.cs` -- `RpgRoller/Components/Pages/Workspace.razor.cs` - -### 7. Update roll visualization and log readability - -Rolemaster open-ended rolls will be hard to interpret if they reuse the current D6-oriented chip styling without extra context. - -Tasks: -- Extend `RollDiceStrip` to distinguish: - - initial percentile roll - - added high-open-ended rolls - - subtracted low-end follow-up rolls -- Add tooltip or title text that explains each die's role in the chain. -- Update compact log summaries so they surface open-ended behavior. -- Ensure private/public visibility behavior remains unchanged. -- Keep roll list rows dense and readable; reserve multi-line breakdown detail for expanded rows or detail fetches. - -Recommended display goals: -- A normal percentile roll should read as simple and uncluttered. -- A high-open-ended roll should clearly show chained additions. -- A low-ended roll should clearly show subtraction, not just a list of numbers. - -Affected areas: -- `RpgRoller/Components/Pages/HomeControls/RollDiceStrip.razor` -- `RpgRoller/Components/Pages/HomeControls/RollDiceStrip.razor.cs` -- `RpgRoller/Components/Pages/Workspace.razor.cs` -- `RpgRoller/Services/GameService.cs` - -### 8. Expand tests to preserve behavior and coverage - -This repo expects very high coverage and already has strong service/API regression tests. Rolemaster support should land with exhaustive tests, not just happy paths. - -Service-level tests to add: -- parse and ruleset mapping for `rolemaster` -- skill creation validation for each Rolemaster roll type -- rejection of invalid fumble ranges -- rejection of D6-only options on Rolemaster skills -- rejection of negative modifiers on D6 / D&D skills -- acceptance of negative modifiers on Rolemaster expressions -- generic standard Rolemaster roll math -- standard percentile roll math -- open-ended high roll with one extra roll -- open-ended high roll with recursive extra rolls -- open-ended low roll with subtraction -- open-ended low roll where the subtract chain itself recursively high-opens -- breakdown formatting and die metadata correctness - -API tests to add: -- create Rolemaster campaign -- create/update skill groups with Rolemaster defaults -- create/update Rolemaster skills -- roll each Rolemaster skill type via API -- verify log page and detail endpoints for Rolemaster rolls -- verify old D6 / D&D contracts still round-trip without requiring Rolemaster-only fields -- verify that a migrated copy of `RpgRoller/App_Data/rpgroller.development.db` still supports legacy D6 API flows after startup migration - -Frontend/regression checks: -- Rolemaster ruleset appears in campaign creation UI -- Rolemaster skill form shows correct conditional fields -- roll results render correctly for open-ended cases -- validation errors are announced and visible for invalid Rolemaster expressions / fumble ranges -- the compact log remains readable and detail expansion shows the full Rolemaster breakdown - -Coverage-sensitive files likely needing tests: -- `RpgRoller.Tests/Services/DiceRulesTests.cs` -- `RpgRoller.Tests/Services/ServiceSkillRollTests.cs` -- new dedicated `Rolemaster` service tests -- `RpgRoller.Tests/Api/CampaignApiTests.cs` -- `RpgRoller.Tests/HostingCoverageTests.cs` -- `RpgRoller.Tests/PayloadBudgetTests.cs` - -### 9. Documentation updates - -The repository instructions require related docs to be updated whenever behavior changes. - -Tasks: -- Update `README.md` to list `Rolemaster` as a supported ruleset. -- Document the three supported Rolemaster roll types. -- Document the open-ended percentile algorithm, including: - - `96+` recursive addition - - `<= fumble range` recursive subtraction -- Document any limits on modifier and fumble range values. -- If API contracts change materially, update `openapi/RpgRoller.json`. - -### 10. Delivery plan in small iterations - -Recommended implementation order: - -#### Iteration 1: ruleset plumbing -- Add `Rolemaster` ruleset enum/id/display support. -- Add campaign creation support. -- Add parser/validator scaffolding for Rolemaster expressions and Rolemaster-only negative modifier support. -- Add baseline tests for ruleset mapping and validation. - -#### Iteration 2: structured skill model -- Preserve `DiceRollDefinition` as the canonical persisted expression and add only the extra persisted Rolemaster fields the expression cannot represent. -- Add migration and schema tests. -- Add startup-migration validation against a copied temp-file instance of `RpgRoller/App_Data/rpgroller.development.db`. -- Update API contracts and service mapping. -- Keep existing D6 / D&D behavior green. - -#### Iteration 3: roll engine -- Implement generic standard and standard percentile execution. -- Implement open-ended percentile execution with recursive high-end chaining and low-end subtraction. -- Extend die-result metadata and breakdown formatting. -- Add focused service tests for roll math and payload/detail serialization compatibility. - -#### Iteration 4: frontend editing -- Update campaign ruleset selection UI. -- Replace D6-only form branching with ruleset-aware skill editing. -- Support Rolemaster skill groups and skills in create/edit flows. -- Add accessible inline validation and progressive disclosure to the Rolemaster editor. -- Verify with ephemeral Playwright. - -#### Iteration 5: log/detail polish and docs -- Improve roll chip rendering and compact summaries for Rolemaster. -- Re-run payload-budget checks and confirm lazy detail loading still holds. -- Update README/OpenAPI/docs. -- Run full local CI and coverage checks. - -## Risks - -- The current skill contract is tightly coupled to D6-era fields, so a shallow patch will create long-term complexity. -- The current `RollDieResult` type is too generic for clear Rolemaster open-ended auditing. -- Allowing negative Rolemaster modifiers requires carefully scoping validation changes so D6/D&D rules do not loosen unintentionally. -- Migration design needs care to avoid breaking legacy SQLite databases. -- A migration that works on an empty test database but fails on the existing D6 production-shaped database would be a release blocker. -- UI complexity will grow if ruleset-specific fields are bolted onto the current `IsD6` logic instead of replacing it with a ruleset-aware form model. -- Richer Rolemaster die metadata could regress payload sizes or list readability if detail is not kept behind the existing lazy-load flow. - -## Recommended Acceptance Criteria - -- A campaign can be created with ruleset id `rolemaster`. -- Rolemaster skill groups and skills can be created and edited through API and UI. -- Supported Rolemaster roll types are: - - generic standard rolls `NdS + x`, with implicit dice count `1` for `dS` - - open-ended percentile `d100! + x` with fumble range -- Open-ended percentile rolls correctly: - - add recursively on first-roll `96+` - - subtract a recursive high-end chain on first-roll `<= fumble range` -- Roll logs and detail views clearly show how a Rolemaster result was produced. -- Existing D6 and D&D 5e flows remain unchanged. -- App startup successfully migrates a copied temp-file instance of `RpgRoller/App_Data/rpgroller.development.db`. -- The migrated copy preserves the existing D6 campaign data and passes legacy D6 read/roll regression checks after startup migration. -- Local CI, coverage checks, and frontend verification all pass.