Add rolemaster auto retry skill toggle
This commit is contained in:
40
TASKS.md
40
TASKS.md
@@ -1,4 +1,4 @@
|
||||
# Rolemaster Automatic Skipp Retry
|
||||
# Rolemaster Automatic Retry
|
||||
|
||||
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
||||
|
||||
@@ -6,17 +6,17 @@ This ExecPlan is a living document. The sections `Progress`, `Surprises & Discov
|
||||
|
||||
## Purpose / Big Picture
|
||||
|
||||
After this change, a Rolemaster skill can opt into an automatic retry when its first result lands in specific “skipp” bands. The player will be able to toggle that behavior while creating or editing a Rolemaster open-ended skill, roll the skill, and then see the retry clearly in the campaign log card through a special badge and readable summary text. The detailed roll view will still show enough information to explain why the retry happened and what final result was recorded.
|
||||
After this change, a Rolemaster skill can opt into an automatic retry when its first result lands in specific retry bands. The player will be able to toggle that behavior while creating or editing a Rolemaster open-ended skill, roll the skill, and then see the retry clearly in the campaign log card through a special badge and readable summary text. The detailed roll view will still show enough information to explain why the retry happened and what final result was recorded.
|
||||
|
||||
For this feature, a “skipp” means a Rolemaster open-ended percentile skill roll whose first fully evaluated result, including the skill expression modifier and any low-end subtraction chain, lands in one of the retry windows before any retry bonus is applied. This plan preserves the user-provided thresholds exactly: results `77` through `90` grant a retry with `+5`; results `91` through `110` grant a retry with `+10`.
|
||||
For this feature, an eligible retry result means a Rolemaster open-ended percentile skill roll whose first fully evaluated result, including the skill expression modifier and any low-end subtraction chain, lands in one of the retry windows before any retry bonus is applied. This plan preserves the user-provided thresholds exactly: results `77` through `90` grant a retry with `+5`; results `91` through `110` grant a retry with `+10`.
|
||||
|
||||
## Progress
|
||||
|
||||
- [x] (2026-04-04 23:52Z) Reviewed `PLANS.md` and the current Rolemaster roll, skill-form, API, and log-card code paths.
|
||||
- [x] (2026-04-04 23:52Z) Authored this ExecPlan in `TASKS.md`.
|
||||
- [ ] Add a persisted per-skill toggle for Rolemaster automatic skipp retry and thread it through API contracts, DTOs, in-memory state, and EF Core migration paths.
|
||||
- [x] (2026-04-14 20:45Z) Added persisted `RolemasterAutoRetry` wiring through the skill model, API contracts, DTOs, in-memory state, clone helpers, EF mapping, and the `20260414204309_AddRolemasterAutoRetry` migration.
|
||||
- [ ] Implement retry-aware Rolemaster roll execution, readable breakdown formatting, and compact log badge/summary output.
|
||||
- [ ] Update Blazor skill create/edit flows so the toggle is shown only when it is valid and stale values are cleared when it is not.
|
||||
- [x] (2026-04-14 20:45Z) Updated the Blazor skill create/edit flows so the automatic retry toggle appears only for Rolemaster open-ended skills and is cleared when the expression stops qualifying.
|
||||
- [ ] Add or update unit, API, persistence, payload-budget, and browser tests that prove the feature end to end.
|
||||
- [ ] Update `README.md`, run `pwsh ./scripts/ci-local.ps1`, and commit the finished implementation.
|
||||
|
||||
@@ -49,13 +49,17 @@ For this feature, a “skipp” means a Rolemaster open-ended percentile skill r
|
||||
Rationale: Recursive retries would make the feature hard to explain, hard to test, and far removed from the user’s “automatic retry” request.
|
||||
Date/Author: 2026-04-04 / Codex
|
||||
|
||||
- Decision: The final stored roll result becomes the retried result plus the retry bonus, while the original skipp result remains visible in the breakdown and log summary.
|
||||
- Decision: The final stored roll result becomes the retried result plus the retry bonus, while the original first result remains visible in the breakdown and log summary.
|
||||
Rationale: An automatic retry should materially change the outcome, not merely annotate the failed first attempt. Keeping the first attempt visible preserves auditability.
|
||||
Date/Author: 2026-04-04 / Codex
|
||||
|
||||
- Decision: The feature uses “retry” terminology throughout the docs and code, with the persisted Boolean named `RolemasterAutoRetry`.
|
||||
Rationale: The user explicitly rejected “skipp” as unclear. `RolemasterAutoRetry` keeps the toggle readable in code, API payloads, and UI text.
|
||||
Date/Author: 2026-04-14 / Codex
|
||||
|
||||
## Outcomes & Retrospective
|
||||
|
||||
Implementation has not started yet. Success for this plan means that a user can enable the toggle on a Rolemaster open-ended skill, produce a first result in the retry band, see the final roll automatically retried with the correct bonus, and recognize that special case directly in the log card without opening the detail view.
|
||||
Milestone 1 is complete. The repo now persists and validates a per-skill `RolemasterAutoRetry` toggle, exposes it in the skill create/edit UI only for Rolemaster open-ended percentile expressions, and round-trips it through service, API, and persistence tests. Roll execution, breakdown formatting, and log surfacing still need to be implemented before the feature is complete end to end.
|
||||
|
||||
## Context and Orientation
|
||||
|
||||
@@ -69,11 +73,11 @@ Campaign log cards are compact list entries, not full detail records. The compac
|
||||
|
||||
## Plan of Work
|
||||
|
||||
Start by extending the skill model so the retry toggle has a place to live. Add a non-nullable Boolean property named `RolemasterRetryOnSkipp` to `Skill` in `RpgRoller/Domain/GameModels.cs`. Thread that property through the DTO surface in `RpgRoller/Contracts/ApiContracts.cs` by extending `CreateSkillRequest`, `UpdateSkillRequest`, `SkillSummary`, and `CharacterSheetSkill`. Keep skill groups unchanged. Update `RpgRoller/Services/GameDtoMapper.cs` so summaries and character sheets include the new flag. Update cloning or state-copy helpers that copy `Skill` objects, including `RpgRoller/Services/GameStateCloneFactory.cs`, so the flag persists through load/save cycles.
|
||||
Start by extending the skill model so the retry toggle has a place to live. Add a non-nullable Boolean property named `RolemasterAutoRetry` to `Skill` in `RpgRoller/Domain/GameModels.cs`. Thread that property through the DTO surface in `RpgRoller/Contracts/ApiContracts.cs` by extending `CreateSkillRequest`, `UpdateSkillRequest`, `SkillSummary`, and `CharacterSheetSkill`. Keep skill groups unchanged. Update `RpgRoller/Services/GameDtoMapper.cs` so summaries and character sheets include the new flag. Update cloning or state-copy helpers that copy `Skill` objects, including `RpgRoller/Services/GameStateCloneFactory.cs`, so the flag persists through load/save cycles.
|
||||
|
||||
Add a database migration for the new column. Update `RpgRoller/Data/RpgRollerDbContext.cs` so EF Core maps `RolemasterRetryOnSkipp` as a required Boolean with a default value of `false`. Generate a migration under `RpgRoller/Migrations` that adds the column to `Skills` with a safe default. Do not widen this migration to unrelated schema changes. If existing migration coverage fixtures or history assertions mention the newest migration id, update them so startup migration tests remain accurate.
|
||||
Add a database migration for the new column. Update `RpgRoller/Data/RpgRollerDbContext.cs` so EF Core maps `RolemasterAutoRetry` as a required Boolean with a default value of `false`. Generate a migration under `RpgRoller/Migrations` that adds the column to `Skills` with a safe default. Do not widen this migration to unrelated schema changes. If existing migration coverage fixtures or history assertions mention the newest migration id, update them so startup migration tests remain accurate.
|
||||
|
||||
Once the property exists, tighten validation. Extend `RpgRoller/Services/SkillDefinitionValidator.cs` so its return value includes the retry flag. Validation must accept `RolemasterRetryOnSkipp = true` only when the ruleset is Rolemaster and the parsed expression kind is `RolemasterOpenEndedPercentile`. For every other ruleset or expression kind, the backend must reject `true` with a specific validation error such as `invalid_rolemaster_retry`. When the flag is false, behavior must remain unchanged. Update `RpgRoller/Services/GameSkillService.cs`, `RpgRoller/Services/IGameService.cs`, and `RpgRoller/Api/SkillEndpoints.cs` so skill creation and update calls carry the extra argument all the way through.
|
||||
Once the property exists, tighten validation. Extend `RpgRoller/Services/SkillDefinitionValidator.cs` so its return value includes the retry flag. Validation must accept `RolemasterAutoRetry = true` only when the ruleset is Rolemaster and the parsed expression kind is `RolemasterOpenEndedPercentile`. For every other ruleset or expression kind, the backend must reject `true` with a specific validation error such as `invalid_rolemaster_retry`. When the flag is false, behavior must remain unchanged. Update `RpgRoller/Services/GameSkillService.cs`, `RpgRoller/Services/IGameService.cs`, and `RpgRoller/Api/SkillEndpoints.cs` so skill creation and update calls carry the extra argument all the way through.
|
||||
|
||||
Implement the retry rule in a dedicated helper instead of burying threshold math inside the roll engine. Add a new backend helper file, for example `RpgRoller/Services/RolemasterRetryPolicy.cs`, with a small API such as `public static int? ResolveSkippRetryBonus(int firstResult)`. This helper must return `5`, `10`, or `null` according to the exact windows described earlier. Put the thresholds here so both tests and the roll engine read the same source of truth.
|
||||
|
||||
@@ -93,9 +97,9 @@ This format is intentionally simple. It is readable in the UI, survives persiste
|
||||
|
||||
Update the compact campaign-log helpers to surface the new special result. `RpgRoller/Services/CampaignLogSummaryBuilder.cs` should accept the stored breakdown when building badges and compact summaries. Add badge codes `rs5` and `rs10` for “Retry +5” and “Retry +10”. When a retry marker is present in the breakdown, append a short retry note to the Rolemaster compact summary, for example `| retry +5` or `| retry +10`, while keeping existing `rf`, `r66`, and `r100` behavior intact. Update `RpgRoller/Components/Pages/HomeControls/CampaignLogPanel.razor.cs` so those new badge codes render as visible labels on the log card.
|
||||
|
||||
After the backend shape is stable, wire the UI toggle. Extend `SkillFormModel` in `RpgRoller/Components/Pages/Home.Models.cs` with `RolemasterRetryOnSkipp`. Update `SkillFormModal.razor.cs` so the form copies the value from `InitialModel`, validates it only for Rolemaster open-ended expressions, and clears it automatically when the skill expression becomes invalid for retry. Update `SkillFormModal.razor` to show a checkbox only in the Rolemaster open-ended branch, near the fumble-range input, with concise help text that explains the exact windows. Update `CharacterPanel.razor.cs` so create and edit skill dialogs pass the property in their initial models and request payloads. Do not add a corresponding group-level control.
|
||||
After the backend shape is stable, wire the UI toggle. Extend `SkillFormModel` in `RpgRoller/Components/Pages/Home.Models.cs` with `RolemasterAutoRetry`. Update `SkillFormModal.razor.cs` so the form copies the value from `InitialModel`, validates it only for Rolemaster open-ended expressions, and clears it automatically when the skill expression becomes invalid for retry. Update `SkillFormModal.razor` to show a checkbox only in the Rolemaster open-ended branch, near the fumble-range input, with concise help text that explains the exact windows. Update `CharacterPanel.razor.cs` so create and edit skill dialogs pass the property in their initial models and request payloads. Do not add a corresponding group-level control.
|
||||
|
||||
Expose the setting in the workspace read model so the user can see it again after save. Extend `WorkspaceState.SkillDefinitionLabel(...)` and `RulesetFormHelpers.DescribeRolemasterExpression(...)` as needed so a Rolemaster open-ended skill with retry enabled renders a label that includes the retry rule in compact form, for example `Open-ended percentile: d100!+15, fumble <= 5, retry skipp`. Keep the label short enough that existing layout remains intact.
|
||||
Expose the setting in the workspace read model so the user can see it again after save. Extend `WorkspaceState.SkillDefinitionLabel(...)` and `RulesetFormHelpers.DescribeRolemasterExpression(...)` as needed so a Rolemaster open-ended skill with retry enabled renders a label that includes the retry rule in compact form, for example `Open-ended percentile: d100!+15, fumble <= 5, auto retry`. Keep the label short enough that existing layout remains intact.
|
||||
|
||||
Finally, update documentation. `README.md` must describe the new Rolemaster skill option, the retry windows, and the fact that the campaign log now shows retry badges. If an example command or screenshot-free narrative is needed, keep it textual and current rather than writing a historical change note.
|
||||
|
||||
@@ -166,7 +170,7 @@ Validation is complete only when all of the following are true.
|
||||
|
||||
The backend proves rule correctness. There must be a unit test where the first attempt result is `78` and the stored final result comes from a retry with `+5`. There must be another where the first attempt result is `96` or another value inside the second band and the stored final result comes from a retry with `+10`. There must be a test where the skill toggle is disabled and an otherwise eligible first result still does not retry.
|
||||
|
||||
The persistence layer proves round-trip safety. A test must create a skill with the toggle enabled, persist the database, reload state, and confirm the skill still exposes `RolemasterRetryOnSkipp = true`.
|
||||
The persistence layer proves round-trip safety. A test must create a skill with the toggle enabled, persist the database, reload state, and confirm the skill still exposes `RolemasterAutoRetry = true`.
|
||||
|
||||
The API layer proves contract shape. A test must create and update a Rolemaster open-ended skill through HTTP and confirm the toggle round-trips through `SkillSummary`, `CharacterSheet`, and roll results. Invalid combinations must return a concrete API error rather than silently coercing the value.
|
||||
|
||||
@@ -211,14 +215,14 @@ At the end of the implementation, these interfaces and shapes must exist.
|
||||
|
||||
In `RpgRoller/Domain/GameModels.cs`, `Skill` must expose:
|
||||
|
||||
public bool RolemasterRetryOnSkipp { get; set; }
|
||||
public bool RolemasterAutoRetry { get; set; }
|
||||
|
||||
In `RpgRoller/Contracts/ApiContracts.cs`, these records must include the new Boolean:
|
||||
|
||||
public sealed record CreateSkillRequest(..., int? FumbleRange = null, bool RolemasterRetryOnSkipp = false);
|
||||
public sealed record UpdateSkillRequest(..., int? FumbleRange = null, bool RolemasterRetryOnSkipp = false);
|
||||
public sealed record SkillSummary(..., int? FumbleRange, bool RolemasterRetryOnSkipp);
|
||||
public sealed record CharacterSheetSkill(..., int? FumbleRange, bool RolemasterRetryOnSkipp);
|
||||
public sealed record CreateSkillRequest(..., int? FumbleRange = null, bool RolemasterAutoRetry = false);
|
||||
public sealed record UpdateSkillRequest(..., int? FumbleRange = null, bool RolemasterAutoRetry = false);
|
||||
public sealed record SkillSummary(..., int? FumbleRange, bool RolemasterAutoRetry);
|
||||
public sealed record CharacterSheetSkill(..., int? FumbleRange, bool RolemasterAutoRetry);
|
||||
|
||||
`RollDieResult` must gain an optional attempt marker:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user