Files
RpgRoller/README.md

14 KiB

RpgRoller

RpgRoller is an ASP.NET Core and Blazor Server app for lightweight tabletop campaign play, character sheets, and dice workflows.

  • RpgRoller/: web app, API endpoints, domain model, EF Core persistence, Blazor components, and static assets
  • RpgRoller.Tests/: xUnit coverage for API behavior, services, hosting, payload budgets, and persistence and migration paths
  • RpgRoller.sln: solution used by local development and repo scripts
  • POSTMORTEM.md: architecture analysis of the May 2026 Firefox and RoboForm failure in the authenticated workspace
  • TASKS.md: the current execution plan for the approved frontend routing rewrite

Test layout:

  • RpgRoller.Tests/Api/: endpoint and host-facing integration tests
  • RpgRoller.Tests/Services/: service and rules-engine tests
  • RpgRoller.Tests/Support/: shared harnesses, builders, and test host helpers

Code Organization

Backend:

  • RpgRoller/Program.cs: app bootstrap, JSON options, compression, API and component mapping, and optional PathBase
  • RpgRoller/Hosting/: service registration, startup initialization, SQLite path resolution, and schema upgrades
  • RpgRoller/Api/: minimal API endpoint groups, request mappings, cookie and session helpers, and result mapping
  • RpgRoller/Services/: gameplay and account workflows behind IGameService
  • RpgRoller/Services/GameService.cs: facade over composed domain services
  • RpgRoller/Services/GameAuthService.cs: registration, login, logout, session lookup, and GetMe
  • RpgRoller/Services/GameCampaignService.cs: campaign creation, listing, roster reads, campaign options, and deletion
  • RpgRoller/Services/GameCharacterService.cs: character creation, updates, activation, deletion, transfer, and owner-scoped reads
  • RpgRoller/Services/GameSkillService.cs: skill-group CRUD, skill CRUD, sheet shaping, and ruleset validation
  • RpgRoller/Services/GameRollService.cs: skill and custom rolls, compact log pages, roll detail, and campaign state snapshots
  • RpgRoller/Services/GameUserAdministrationService.cs: username reads, admin user listing, role updates, and account deletion
  • RpgRoller/Services/GameStateStore.cs, GameStateCloneFactory.cs, and GamePersistenceService.cs: in-memory runtime state, campaign-state version tracking, and SQLite load and save boundaries
  • RpgRoller/Services/GameAuthorization.cs, GameContextResolver.cs, and GameDtoMapper.cs: shared authorization, session and campaign resolution, and backend read-model mapping
  • RpgRoller/Services/RollEngine.cs, StandardRollEngine.cs, D6RollEngine.cs, RolemasterRollEngine.cs, RollBreakdownFormatter.cs, and CampaignLogSummaryBuilder.cs: ruleset-specific dice execution, breakdown formatting, and compact campaign-log summaries
  • RpgRoller/Services/SkillDefinitionValidator.cs, RoleSerializer.cs, RollVisibilityParser.cs, and CustomRollOptionsResolver.cs: shared rules and parsing helpers

Frontend:

  • RpgRoller/Components/App.razor: current HTML shell and the request-time branch that decides whether / serves the static auth page or the interactive app
  • RpgRoller/Components/Routes.razor: Blazor router and layout hookup
  • RpgRoller/Components/Layout/MainLayout.razor: default layout
  • RpgRoller/Components/Pages/Home.razor: current root route component for /; it only renders Workspace
  • RpgRoller/Components/Pages/Home.razor.cs: logout navigation helper that force-loads / and carries auth status query text
  • RpgRoller/Components/Pages/Workspace.razor: authenticated workspace UI with play, campaign management, admin, toasts, and modals
  • RpgRoller/Components/Pages/Workspace.razor.cs: workspace composition root, lifecycle, coordinator wiring, JS-invokable entry points, and menu item construction
  • RpgRoller/Components/Pages/WorkspaceState.cs: workspace UI state plus pure computed and formatting projections used directly by the Razor view
  • RpgRoller/Components/Pages/WorkspaceSessionCoordinator.cs, WorkspaceCampaignCoordinator.cs, WorkspaceCampaignScopeCoordinator.cs, WorkspacePlayCoordinator.cs, WorkspaceAdminCoordinator.cs, WorkspaceLiveStateController.cs, WorkspaceFeedbackService.cs, and WorkspaceToast.cs: session bootstrap, campaign scope, play and log, admin, live update, and toast concerns used by Workspace
  • RpgRoller/Components/Pages/HomeControls/StaticAuthPage.razor: plain HTML login and registration page used when / is requested without a valid session
  • RpgRoller/Components/Pages/HomeControls/: workspace child components, forms, header, panels, and modal controls
  • RpgRoller/Components/RpgRollerApiClient.cs: browser API client for write actions
  • RpgRoller/Components/WorkspaceQueryService.cs: browser-facing read client for workspace data
  • RpgRoller/wwwroot/js/rpgroller-api.js: browser interop for auth forms, session storage, SSE wiring, and DOM helpers
  • RpgRoller/wwwroot/styles.css: app styling and responsive layout

Current repo note:

  • POSTMORTEM.md documents why the current authenticated workspace architecture is fragile and why the next major frontend change is a route-first rewrite of the authenticated shell.
  • TASKS.md is the authoritative execution plan for that rewrite and must be kept current while the work proceeds.

Runtime and Persistence

  • Persistence uses EF Core with SQLite (Microsoft.EntityFrameworkCore.Sqlite).
  • The default database file is RpgRoller/App_Data/rpgroller.db.
  • ConnectionStrings__RpgRoller overrides the SQLite path for local runs, tests, or temporary environments.
  • Startup applies pending EF Core migrations through Database.Migrate().
  • The app loads runtime state into memory during startup and persists successful state changes back to SQLite.
  • RpgRoller/App_Data/rpgroller.development.db is a checked-in migration coverage fixture used by hosting tests that copy it to a temporary file before validation.

Product Capabilities

  • Supported campaign rulesets: D6 System, D&D 5e, and Rolemaster
  • Account registration, login, session-based auth, and role-aware authorization
  • Admin tools for user listing, role updates, account deletion, and direct SQLite database download
  • Campaign creation, roster reads, participant-scoped visibility, and owner and admin deletion
  • Character creation, activation, owner transfer, campaign reassignment or unlinking, and owner and admin deletion
  • Skill groups with reusable defaults plus skill and skill-group create, edit, reassign, and delete flows
  • Owner-scoped play workspace that lists only the current user's characters while preserving GM and admin management capabilities
  • Campaign log paging, lazy-loaded roll detail, compact summaries, and live state refresh through SSE
  • Custom roll submission from the play screen without creating a persisted skill
  • Instant skill filtering in the character panel
  • Campaign management owner labels based on display names

Rolemaster support:

  • Standard expressions such as d10, 15d10, 2d10+48, and d100-15
  • Open-ended percentile expressions such as d100!+85
  • Conditional FumbleRange handling for open-ended percentile skills and skill-group defaults
  • Persisted and validated automatic retry toggle for open-ended percentile skills; only eligible Rolemaster skills can enable it
  • Rolemaster skill rolls open a modal prompt before rolling so the player can apply a one-shot situational modifier; the prompt autofocuses, supports Enter and Escape, and closes when clicking outside it
  • One-shot situational modifiers are transient Rolemaster-only roll inputs; the temporary modifier is applied to both the first attempt and any automatic retry attempt
  • Automatic retry windows for eligible open-ended skills: results 76-90 retry once with +5, and results 91-110 retry once with +10
  • Open-ended high chaining and low-end subtraction with ordered die metadata in roll detail
  • Compact log badges and summaries for open-ended, retry, and fumble-related events, including Retry +5 and Retry +10

Current Frontend Architecture

The current frontend is in an intermediate state that was created while mitigating the Firefox and RoboForm failure documented in POSTMORTEM.md.

Today, / is dual-purpose:

  • when the request has no valid session cookie, RpgRoller/Components/App.razor renders StaticAuthPage.razor as plain HTML and RpgRoller/wwwroot/js/rpgroller-api.js handles login and registration through fetch
  • when the request has a valid session cookie, App.razor renders the interactive Blazor app and Home.razor loads the authenticated Workspace

Inside the authenticated app, the hamburger menu does not navigate to different URLs. Instead, WorkspaceSessionCoordinator.cs stores a screen preference in sessionStorage, and Workspace.razor conditionally swaps between play, campaign management, and admin screens inside one large component tree.

This architecture works functionally but remains structurally fragile because:

  • the root shell still branches on request-time HttpContext
  • the authenticated workspace still performs staged startup in OnAfterRenderAsync
  • the app coordinates state across Blazor component state, browser sessionStorage, fetch, and SSE during early startup
  • the route URL does not represent the authenticated screen the user is actually viewing

Approved Rewrite Direction

The approved remediation direction is a route-first authenticated shell:

  • / becomes an auth-aware entry point that redirects to /login or /play
  • /login hosts the anonymous auth experience
  • /play, /campaigns, and /admin become real authenticated routes
  • the hamburger menu becomes route navigation instead of in-memory screen switching
  • SSE and heavy play bootstrap stay scoped to /play
  • the large Workspace component is split so each route owns a smaller, more stable subtree

This rewrite is not complete yet. Follow TASKS.md for the execution plan.

Local Development

Prerequisites:

  • .NET SDK 10.0+
  • PowerShell 7+
  • Node.js 22+

Initial setup:

dotnet tool restore
npm ci
npm exec playwright install chromium

Run locally:

  1. Start the app:
    dotnet run --project RpgRoller/RpgRoller.csproj
    
  2. Open http://localhost:5000 or the URL printed in the console.
  3. Expect the current app to show either the static auth page at / or the authenticated workspace at /, depending on whether a valid session cookie already exists.

Playwright helpers:

  • Run the checked-in smoke suite against an isolated temporary SQLite database:
    pwsh ./scripts/run-playwright.ps1
    
  • Run Playwright directly when the app is already running:
    npm run e2e
    

VS Code launch profiles in .vscode/launch.json:

  • RpgRoller: Server
  • RpgRoller: Server + Edge (F5)
  • RpgRoller: Server + Firefox (F5)

Environment overrides:

  • Set ConnectionStrings__RpgRoller to point at a custom SQLite database.
  • Set PathBase to host the app under a sub-path such as /rpgroller.

Migration authoring:

dotnet dotnet-ef migrations add <MigrationName> --project RpgRoller/RpgRoller.csproj --startup-project RpgRoller/RpgRoller.csproj

SQLite migration rule:

  • Keep table-rebuild operations separate from unrelated schema or data changes so EF Core does not emit non-transactional migration warnings.

Frontend Runtime

  • The UI currently runs as Blazor Server with interactive components for the authenticated workspace and a plain HTML plus JavaScript auth page for anonymous users at /.
  • Static assets are linked through Blazor's @Assets[...] pipeline for fingerprinted cache-busting URLs.
  • Workspace reads are resolved through API requests in WorkspaceQueryService; browser interop stays focused on auth forms, session storage, SSE wiring, and small DOM helpers.
  • Live workspace refresh compares separate roster, per-character sheet, and log versions so unrelated changes do not trigger full reloads.
  • Campaign log data is loaded in bounded slices: campaign summaries, one selected roster, one selected character sheet, and a 25-row incremental log window from /api/campaigns/{campaignId}/log/page.
  • Log rows return compact summary data first and lazy-load full detail from /api/rolls/{rollId} when expanded.
  • Newly appended local rolls auto-expand in the play workspace and reuse the roll response as the initial detail payload.
  • Custom roll submission uses the selected character context; D6 uses baseline wild-die and fumble behavior, while D&D 5e and Rolemaster use the submitted expression directly.
  • API JSON contracts use the source-generated RpgRollerJsonSerializerContext.
  • HTTP JSON responses are gzip-compressed when the client advertises support.
  • The OpenAPI contract source lives at openapi/RpgRoller.json.

Test and Coverage

  • Test command:
    dotnet test RpgRoller.Tests/RpgRoller.Tests.csproj --collect:"XPlat Code Coverage" --settings RpgRoller.Tests/coverlet.runsettings
    
  • Coverage gate:
    pwsh ./scripts/check-coverage.ps1 -MinLineRate 0.90 -MinBranchRate 0.70
    
  • Local parity script:
    pwsh ./scripts/ci-local.ps1
    
  • scripts/ci-local.ps1 writes coverage collector output to a unique temporary results directory outside the repo, reads coverage from there, removes that directory at the end of the run, and sweeps stray coverage.cobertura.xml files from RpgRoller.Tests/TestResults.
  • Regression tests enforce payload budgets for character sheet reads, initial and incremental campaign log loads, roll mutation responses, and lazy-loaded Rolemaster roll detail payloads.
  • RpgRoller.Tests/coverlet.runsettings measures the full RpgRoller backend assembly.