Files
RpgRoller/TASKS.md

20 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:

  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 d100s
    • add all follow-up rolls
  • Else if first roll <= fumble range:
    • roll one or more high-ended follow-up d100s
    • 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.
  • 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.