19 KiB
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:
- Standard expressions:
NdS + x, with an implicit dice count of1when no digit appears befored - Open-ended percentile:
d100! + xwith 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
rolemasterruleset at campaign creation time. - Rolemaster skills are ruleset-specific and should not reuse D6-only options such as
WildDiceandAllowFumble. - Open-ended percentile behavior is defined as:
- Roll the first
d100. - If the first roll is
96or higher, roll another high-endedd100recursively and add it. - If the first roll is less than or equal to the skill's configured fumble range, roll another high-ended
d100recursively and subtract it. - Recursive follow-up rolls are high-ended only.
- Roll the first
- Standard Rolemaster rolls do not open-end and do not use a fumble range.
- The modifier
xshould 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!
- standard roll with implicit count:
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
RollDieResultshape. - 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
RolemastertoRulesetKind. - Update
DiceRules.TryParseRulesetId. - Update
DiceRules.ToRulesetId. - Add
rolemastertoDiceRules.SupportedRulesets. - Update tests that enumerate supported rulesets and campaign creation behavior.
Affected areas:
RpgRoller/Domain/GameModels.csRpgRoller/Services/DiceRules.csRpgRoller/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:
DiceRollDefinitionWildDiceAllowFumble
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
DiceRollDefinitionas 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
DiceRollDefinitionas 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.csRpgRoller/Contracts/ApiContracts.csRpgRoller/Services/GameService.csRpgRoller/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:
RolemasterStandard- supports generic
NdSsyntax with any supported side count - assumes a dice count of
1when the count is omitted befored - integer modifier required
- no fumble range
- supports generic
RolemasterOpenEndedPercentile- fixed base roll
1d100 - integer modifier required
- fumble range required and validated
- fumble range must be below
96
- fixed base roll
- 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.ParseExpressionwith a Rolemaster-focused parser/validator path. - Add canonical Rolemaster expression parsing rules:
d10,2d10+48, and15d10-15for generic standard rollsd100+4andd100-20for standard percentiled100!+85andd100!-15for 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: d10Rolemaster: 15d10+15Open-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.csRpgRoller/Services/GameService.csRpgRoller.Tests/Services/DiceRulesTests.csRpgRoller.Tests/Services/ServiceSkillGroupAndOwnershipTests.csRpgRoller.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:
ComputeRolldispatches by ruleset and roll kind.- Add dedicated methods:
ComputeRolemasterStandardRollComputeRolemasterOpenEndedRoll- 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
d100s - add all follow-up rolls
- roll one or more high-ended follow-up
- Else if first roll
<= fumble range:- roll one or more high-ended follow-up
d100s - subtract the follow-up total
- roll one or more high-ended follow-up
- 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
ComputeRolldispatch. - 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
RollDieResultDTO is sufficient.- likely not sufficient, because it only has
Crit,Fumble,Wild,Removed,Added
- likely not sufficient, because it only has
- Recommended DTO extension:
SequenceorOrderPhase/Kindsuch asinitial,open-ended-high,open-ended-low-subtractSignedContributionor 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.csRpgRoller/Contracts/ApiContracts.csRpgRoller/Contracts/RpgRollerJsonSerializerContext.csRpgRoller/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
SkillandSkillGroupEF models. - Update
RpgRollerDbContextconfiguration. - Add EF Core migration for new Rolemaster fields.
- Preserve existing data for D6 / D&D 5e campaigns.
- Decide migration defaults for legacy rows.
- Update
SqliteSchemaUpgradercoverage 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.csRpgRoller/Migrations/*RpgRoller/Hosting/SqliteSchemaUpgrader.csRpgRoller.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
IsD6branching with a ruleset-aware UI model. - Extend skill and skill-group request/response contracts with Rolemaster fields.
- Ensure campaign creation UI includes
Rolemasterin 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.csRpgRoller/Components/Pages/Home.Models.csRpgRoller/Components/Pages/HomeControls/SkillFormModal.razorRpgRoller/Components/Pages/HomeControls/SkillFormModal.razor.csRpgRoller/Components/Pages/HomeControls/CharacterPanel.razorRpgRoller/Components/Pages/HomeControls/CharacterPanel.razor.csRpgRoller/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
RollDiceStripto 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.razorRpgRoller/Components/Pages/HomeControls/RollDiceStrip.razor.csRpgRoller/Components/Pages/Workspace.razor.csRpgRoller/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.dbstill 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.csRpgRoller.Tests/Services/ServiceSkillRollTests.cs- new dedicated
Rolemasterservice tests RpgRoller.Tests/Api/CampaignApiTests.csRpgRoller.Tests/HostingCoverageTests.csRpgRoller.Tests/PayloadBudgetTests.cs
9. Documentation updates
The repository instructions require related docs to be updated whenever behavior changes.
Tasks:
- Update
README.mdto listRolemasteras a supported ruleset. - Document the three supported Rolemaster roll types.
- Document the open-ended percentile algorithm, including:
96+recursive addition<= fumble rangerecursive 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
Rolemasterruleset 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
DiceRollDefinitionas 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
RollDieResulttype 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
IsD6logic 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 count1fordS - open-ended percentile
d100! + xwith fumble range
- generic standard rolls
- Open-ended percentile rolls correctly:
- add recursively on first-roll
96+ - subtract a recursive high-end chain on first-roll
<= fumble range
- add recursively on first-roll
- 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.