diff --git a/TASKS.md b/TASKS.md index e69de29..76d9e2d 100644 --- a/TASKS.md +++ b/TASKS.md @@ -0,0 +1,357 @@ +# Rolemaster Support Plan + +## Goal + +Extend the app with a new `rolemaster` ruleset that supports three Rolemaster-oriented skill roll types: + +1. Initiative: `2d10 + x` +2. Standard percentile: non-open-ended `1d100 + x` +3. Open-ended percentile: `1d100 + x` with Rolemaster low-end and high-end behavior + +The initial scope is only roll definition, validation, execution, logging, and UI support for these three roll types. 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 percentile rolls do not open-end and do not use a fumble range. +- Initiative rolls are simple `2d10 + x` rolls with no open-ended behavior. +- The modifier `x` should be an integer and may be negative if we want Rolemaster penalties to be expressible. Current generic dice validation only allows non-negative modifiers, so this must be revisited. +- Existing D6 and D&D 5e behavior must remain unchanged. + +## Open Questions To Resolve Before Implementation + +- Should the Rolemaster modifier allow negative values everywhere, or only for Rolemaster skills? +- What is the allowed fumble range for open-ended percentile skills: + - `0-5` + - `0-10` + - unrestricted `0-100` +- Should a low-ended first roll that is also `96+` ever be possible through configuration overlap: + - recommended answer: no, validation should prevent impossible overlap by constraining fumble range below `96` +- How should roll breakdown text read for low-ended subtraction: + - recommended format: `12 - (97 + 44) + 15 = -114` +- Should initiative be represented as a dedicated Rolemaster roll type, or as a generic fixed expression with Rolemaster labeling: + - recommended answer: dedicated type, because UI copy and validation differ +- Do skill groups need to support Rolemaster defaults for all of these fields: + - recommended answer: yes, to stay consistent with current prototype behavior + +## 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 move toward a ruleset-aware skill definition that can express roll behavior directly instead of encoding everything into a free-form expression string. + +Recommended model additions: +- `SkillRollKind` enum + - `StandardExpression` + - `RolemasterInitiative` + - `RolemasterPercentile` + - `RolemasterOpenEndedPercentile` +- `RollModifier` integer +- `RolemasterFumbleRange` nullable integer +- keep `DiceRollDefinition` only if needed for legacy display or migration compatibility + +Recommended direction: +- Keep existing fields working for D6 and D&D 5e. +- Add new fields for Rolemaster rather than overloading `WildDice` / `AllowFumble`. +- Centralize skill validation into a single ruleset-aware validator that returns a canonical internal representation. + +Tasks: +- Define a ruleset-aware skill definition model in domain/contracts. +- Decide whether to preserve `DiceRollDefinition` as persisted state or derive display text from structured fields. +- 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: +- `RolemasterInitiative` + - fixed base roll `2d10` + - integer modifier required + - no fumble range +- `RolemasterPercentile` + - fixed base roll `1d100` + - 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 + +Tasks: +- Replace or extend `ValidateSkillDefinition`. +- Add a Rolemaster-focused parser/validator path. +- Decide whether `DiceRules.ParseExpression` should remain only for free-form rulesets or become a lower-level helper. +- Add canonical display formatting for Rolemaster skills, for example: + - `Initiative: 2d10+15` + - `Percentile: 1d100+48` + - `Open-ended percentile: OE 1d100+48, fumble <= 5` + +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: + - `ComputeRolemasterInitiativeRoll` + - `ComputeRolemasterPercentileRoll` + - `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. +- 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. + +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. + +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. + +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. +- Replace "Expression" with a Rolemaster-specific editor when the selected campaign ruleset is `rolemaster`. +- Add a "Roll type" selector with options: + - `Initiative` + - `Percentile` + - `Open-ended percentile` +- Show modifier input for all Rolemaster roll types. +- Show fumble range input only for open-ended percentile. + +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. + +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 +- initiative 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 + +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 + +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` + +### 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 roll types. +- Add baseline tests for ruleset mapping and validation. + +#### Iteration 2: structured skill model +- Add persisted Rolemaster fields for skills and skill groups. +- Add migration and schema tests. +- Update API contracts and service mapping. +- Keep existing D6 / D&D behavior green. + +#### Iteration 3: roll engine +- Implement initiative 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. + +#### 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. +- Verify with ephemeral Playwright. + +#### Iteration 5: log/detail polish and docs +- Improve roll chip rendering and compact summaries for Rolemaster. +- 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 may require carefully scoping validation changes so D6/D&D rules do not loosen unintentionally. +- Migration design needs care to avoid breaking legacy SQLite databases. +- 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. + +## 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 limited to: + - initiative `2d10 + x` + - standard percentile `1d100 + x` + - open-ended percentile `1d100 + 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. +- Local CI, coverage checks, and frontend verification all pass.