Skip to content

feat(rdm): RDM manager — victim reports, staff case queue, slay punishment#40

Open
GabeHirakawa wants to merge 19 commits into
mainfrom
feat/rdm-manager
Open

feat(rdm): RDM manager — victim reports, staff case queue, slay punishment#40
GabeHirakawa wants to merge 19 commits into
mainfrom
feat/rdm-manager

Conversation

@GabeHirakawa

Copy link
Copy Markdown

RDM Manager

Ports the SourceMod/CS:GO TTT RDM (Random Death Match) manager into a new engine-agnostic TTT/RDM module. Adds a victim-driven reporting + staff case-queue + slay-punishment workflow alongside the existing auto-karma system (Karma is left functionally intact).

Design spec: docs/superpowers/specs/2026-06-28-rdm-manager-design.md
Implementation plan (12 tasks): docs/superpowers/plans/2026-06-28-rdm-manager.md

Flow

  1. A suspect kill (per the SourceMod "bad kill" rules — same-role teamkills or any non-Traitor-vs-non-Traitor kill) is logged, and the victim is auto-prompted.
  2. The victim opts in via !rdm (lists their recent suspect deaths; !rdm <n> [reason] files a report). Non-response defaults to "not RDM" — no case is created.
  3. The report becomes a persisted case in a staff queue.
  4. Staff work the queue with chat commands: !cases (count + list), !info <id> (details + karma evidence), !handle [id] (claim oldest/specific), !verdict guilty|forgive.
  5. A guilty verdict queues N slays scaled by victim role (traitor 5 / detective 5 / innocent 3, configurable), paid down at the start of upcoming rounds, with an immediate slay if the offender is alive. Forgive closes the case with no punishment.

Architecture

  • Shared IDamageTracker extracted from KarmaListener into TTT/Game/Damage (option A) so suspect/fault detection has one source of truth; KarmaListener behavior is preserved (existing tests unchanged).
  • SQLite persistence (rdm.db) for deaths, cases, and slay debts, so cases and owed slays survive round changes, map changes, and restarts. IRdmStore has an in-memory impl (tests) and a SqliteRdmStore (production), the latter serialized with a SemaphoreSlim for its shared connection.
  • New module self-registers via RdmServiceCollection.AddRdmService(); CS2 cvars (css_ttt_rdm_*) via CS2RdmConfig, mirroring CS2KarmaConfig.
  • Staff commands gated on @ttt/admin (same flag as !logs), configurable via RdmConfig.StaffFlag.

Testing

  • ~40 new tests across the RDM module plus an end-to-end test (suspect kill → report → handle → guilty → slay debt).
  • 234 passed / 1 failed; the single failure (ModuleInitializationTest.Started_ShouldNotBeInit_IfNotStarted) is a pre-existing order-dependent flake on dev, not introduced by this branch (zero new failures).
  • TTT/Test and TTT/Plugin both build with 0 errors.

Built task-by-task with a fresh implementer + independent reviewer per task, plus a final whole-branch review (which caught and fixed two production-only issues invisible to the test suite: the CS2 cvar registration and the SQLite single-connection concurrency hazard).

Heads-up before merge

  • Commits are currently unsigned (local commit signer was unavailable during development). Re-sign via rebase before merge if branch protection requires signed commits.
  • Non-blocking follow-ups (documented, not addressed here): unbounded deaths table / session-scoping of the round counter; a few coverage tests (report-window & per-round-cap guards, offline-debtor slay preservation); minor polish (verdict displayed-count nuance, config getter caching).

https://claude.ai/code/session_015VpDpUwhCqDKEHWofQfjXH

Victim-driven RDM reporting + staff case queue (chat commands) + slay-queue
punishment, persisted to SQLite. Sits alongside existing auto-karma; reuses an
extracted shared IDamageTracker for suspect-kill detection.

Claude-Session: https://claude.ai/code/session_015VpDpUwhCqDKEHWofQfjXH
12 TDD tasks: module scaffold, shared IDamageTracker extraction, suspect
classifier, SQLite store, death-log + victim prompt, case manager, !rdm,
staff !cases/!info/!handle/!verdict, slay queue, production wiring + e2e.

Claude-Session: https://claude.ai/code/session_015VpDpUwhCqDKEHWofQfjXH
- CasesCommand: staff-gated !cases lists open RDM case count + brief entries
- InfoCommand: staff-gated !info <id> shows full case details (Args[1] corrected from brief's Args[0])
- New locale keys: RDM_CASES_COUNT, RDM_CASES_ENTRY, RDM_INFO, RDM_CASE_NOT_FOUND
- FakePermissionManager: add SetFlags for per-player flag grants; HasFlags now enforces when flags required
- LogsTest: grant @ttt/admin explicitly (LogsCommand requires it; old blanket-true fake masked this)
…eview)

- CS2RdmConfig.Start(BasePlugin?) now calls plugin.RegisterFakeConVars(this)
  so css_ttt_rdm_* cvars are registered in the engine on plugin load
- SqliteRdmStore wraps all 11 IRdmStore methods with SemaphoreSlim(1,1)
  gate to prevent concurrent access on the single shared connection
- SlayQueueListener faulted-task continuation logs payout errors via Messenger
- RdmCommand.Description corrected (no dismiss path exists)
@github-actions

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
ShopAPI 15% 3% 88
RDM 89% 68% 227
API 45% 32% 102
Karma 64% 59% 132
Shop 24% 18% 410
Game 73% 57% 474
Locale 72% 65% 212
Summary 59% (2189 / 3697) 46% (698 / 1510) 1645

Base automatically changed from dev to main July 12, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant