397 lines
19 KiB
Markdown
397 lines
19 KiB
Markdown
# 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.
|
|
- 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.
|
|
|
|
## 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`
|
|
- Initiative should be represented as a dedicated Rolemaster roll type, because UI copy and validation differ
|
|
- 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:
|
|
- initiative: `2d10+48`
|
|
- standard percentile: `d100+4`
|
|
- open-ended percentile: `d100!+85`
|
|
- negative modifiers are valid only for Rolemaster, for example `d100-15`
|
|
|
|
## 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.
|
|
- 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:
|
|
- `SkillRollKind` enum
|
|
- `StandardExpression`
|
|
- `RolemasterInitiative`
|
|
- `RolemasterPercentile`
|
|
- `RolemasterOpenEndedPercentile`
|
|
- `RolemasterFumbleRange` nullable integer
|
|
- keep `DiceRollDefinition` as the canonical persisted expression across rulesets
|
|
- derive `SkillRollKind` and `RollModifier` from parsed expressions unless persisting them provides a concrete implementation benefit
|
|
|
|
Recommended direction:
|
|
- 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:
|
|
- `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
|
|
- 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:
|
|
- `2d10+48` and `2d10-15` for initiative
|
|
- `d100+4` and `d100-20` for standard percentile
|
|
- `d100!+85` and `d100!-15` for open-ended percentile
|
|
- Keep Rolemaster fumble range validation separate from the expression because it is configured independently.
|
|
- Add canonical display formatting for Rolemaster skills, for example:
|
|
- `Initiative: 2d10+15`
|
|
- `Percentile: 1d100+48`
|
|
- `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:
|
|
- `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.
|
|
- 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.
|
|
|
|
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.
|
|
- Keep "Expression" as the primary input, but provide Rolemaster-specific help text, examples, and validation when the selected campaign ruleset is `rolemaster`.
|
|
- Add a "Roll type" selector with options:
|
|
- `Initiative`
|
|
- `Percentile`
|
|
- `Open-ended percentile`
|
|
- Keep the selector and expression input synchronized so users can either choose a type or see the parsed/canonical expression clearly.
|
|
- Show fumble range input only for open-ended percentile.
|
|
- Use progressive disclosure so only the fields relevant to the chosen Rolemaster roll type 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
|
|
- 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
|
|
- verify old D6 / D&D contracts still round-trip without requiring Rolemaster-only fields
|
|
|
|
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.
|
|
- 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 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.
|
|
- 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 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.
|