Files
RpgRoller/TASKS.md
2026-04-02 01:44:07 +02:00

18 KiB

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
  • 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
    • 15d10 e.g. for spells indicating "heals 1d10/lvl hit points".

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:

  • 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 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.

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.
  • 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.