Meta

Reports

Report-a-player: rate-capped, recorded, surfaced via an admin decorator.

Server
services.AddCrossplayReports();
Unity package
com.crossplay.reports
Depends on
core

Providers you can swap 2

Infrastructure seams. Crossplay ships a working implementation of each — replacing one changes where data lives or how it moves, never a game rule.

IReportClock provider

Wall-clock seam (unix UTC ms) so rate-cap math is testable. Piece-local on purpose.

  • long UtcNowMs { get; }
IReportStore provider

Persistence seam for filed reports. The default InMemoryReportStore is single-node and volatile; register a document-backed implementation BEFORE AddCrossplayReports (e.g. via AddCrossplayPersistentStores) to survive restarts / share across a cluster. Genre-neutral: it stores opaque ReportRecord rows and never interprets the category/details.

  • int CountByReporterSince(long reporterAccountId, long sinceUtcMs)

    How many reports reporterAccountId filed at or after sinceUtcMs (unix UTC ms) — backs the per-hour rate cap.

  • long NextId()

    Allocates the next unique report id.

  • IReadOnlyList<ReportRecord> Recent(int count)

    The newest reports first, up to count — the operators' review feed.

  • void Save(ReportRecord record)

    Persists a filed report.

Services you call 1

Crossplay implements these. Resolve them from DI and call them from your own systems.

IReportService

The report-a-player pipeline: validate the target + details, enforce the per-hour rate cap, then record. Genre-neutral — the category byte and details string are the game's vocabulary. Inject and call from the game's own systems; the built-in ReportHandlers already wires the report wire message to it.

  • IReadOnlyList<ReportRecord> Recent(int count)

    The freshest reports (for the operators' review surface).

  • bool TryReport(ISession session, string targetName, byte category, string details, out ushort reason)

    Files a report by session against the player named targetName: checks the details length, resolves the target online, applies the rate cap, then records it.

Configuration 1

Every tunable lives in an options object — there are no magic numbers to hunt for.

ReportsOptions

Reports tuning (defaults here, never inline).

  • int AdminPageSize { get; set; }

    How many recent reports the admin surface returns.

  • int MaxDetailsLength { get; set; }

    Longest allowed details text.

  • int MaxReportsPerHour { get; set; }

    Reports one account may file per hour (0 = uncapped).

Wire messages 2

The protocol this piece speaks. Ids are allocated per piece so they can never collide.

3501 ReportRequest

Client → server: report a player. The category byte and details string are the GAME's vocabulary — the framework only records them for the operators' review surface.

3502 ReportResponse

Server → client: the report verdict.

Unity services you inject 1

Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.

IReportClient

The Reports piece, client side: report a player. The category byte and details string are the GAME's vocabulary — this client never interprets them.

  • UniTask<ReportResponse> ReportAsync(string targetName, byte category, string details, CancellationToken ct = default)

    Files a report (refusals carry the reason code).

UI views you can replace 1

Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.

ICrossplayReportsView

Narrow contract the presenter drives. UI-only: render calls in, user intents out.