Skip to content

feat: shooter/sniper genre-specific checks MVP#2

Merged
zaferdace merged 6 commits intomainfrom
feat/shooter-genre-mvp
Mar 29, 2026
Merged

feat: shooter/sniper genre-specific checks MVP#2
zaferdace merged 6 commits intomainfrom
feat/shooter-genre-mvp

Conversation

@zaferdace
Copy link
Copy Markdown
Owner

@zaferdace zaferdace commented Mar 29, 2026

Summary

  • 3 new Shipcheck rules for shooter/sniper games (weapon remote trust, spawn clustering, combat content maturity)
  • 2 new Playtester presets (weapon equip flow, respawn cycle)
  • Genre checks are opt-in and heuristic/pattern-based

New Tools

Tool Category Description
rbx_shooter_weapon_remote_trust Security Audit weapon remotes for server-side validation
rbx_shooter_spawn_clustering Gameplay Detect spawn clustering and team imbalance
rbx_shooter_combat_content_maturity Content Combat keyword scanning for age rating

New Playtester Presets

  • shooter_weapon_equip — verify weapon Tools with config values
  • shooter_respawn_cycle — validate respawn infrastructure

Test plan

  • npm run build passes
  • npm run check passes
  • Studio test: run new tools against a shooter project
  • Verify genre checks don't affect non-shooter projects

🤖 Generated with Claude Code

Summary by Sourcery

Add shooter-genre-specific analysis tools and playtester presets, and integrate additional code quality automation.

New Features:

  • Introduce three shooter-focused Shipcheck tools for weapon remote validation, spawn clustering, and combat content maturity scanning.
  • Add two shooter playtester scenario presets for weapon equip verification and respawn cycle validation.

Enhancements:

  • Extend the playtester scenario schema to support selecting shooter-specific presets.

Build:

  • Add Sourcery configuration for automated code review with path exclusions.

CI:

  • Add a SonarCloud scan job to the CI workflow for static analysis on pushes and pull requests.

zaferdace and others added 3 commits March 29, 2026 21:23
- .sourcery.yaml: auto-review PRs, ignore dist/plugin/json/md
- CI workflow: add sonarcloud job with SonarSource scan action

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Switch from sonarqube-scan-action to sonarcloud-github-action@v5
- Add Node.js setup + npm ci + build before SonarCloud scan
- Remove redundant args (read from sonar-project.properties)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shipcheck rules (opt-in, genre: shooter):
- rbx_shooter_weapon_remote_trust: audit weapon remotes for server-side
  validation, type checking, and rate limiting
- rbx_shooter_spawn_clustering: detect spawn point clustering, team
  imbalance, and suspicious spawn heights
- rbx_shooter_combat_content_maturity: scan scripts and UI for combat
  keywords affecting content maturity classification

Playtester presets:
- shooter_weapon_equip: verify weapon Tools exist with config values
- shooter_respawn_cycle: validate respawn infrastructure

All genre checks are heuristic and pattern-based.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 29, 2026

Reviewer's Guide

Adds three new genre-specific Shipcheck tools for Roblox shooter/sniper games (weapon remote trust, spawn clustering, and combat content maturity), two shooter-focused Playtester presets, and introduces SonarCloud + Sourcery configuration to CI and repo tooling.

Sequence diagram for shooter weapon remote trust analysis

sequenceDiagram
  participant ShipcheckTool as WeaponRemoteTrustTool
  participant StudioBridgeClient
  participant RobloxStudio

  ShipcheckTool->>StudioBridgeClient: new StudioBridgeClient(port)
  ShipcheckTool->>StudioBridgeClient: ping()
  StudioBridgeClient-->>ShipcheckTool: pong

  ShipcheckTool->>StudioBridgeClient: searchInstances(RemoteEvent,class)
  StudioBridgeClient->>RobloxStudio: query RemoteEvent instances
  RobloxStudio-->>StudioBridgeClient: RemoteEvent matches
  StudioBridgeClient-->>ShipcheckTool: remoteEventMatches

  ShipcheckTool->>StudioBridgeClient: searchInstances(RemoteFunction,class)
  StudioBridgeClient->>RobloxStudio: query RemoteFunction instances
  RobloxStudio-->>StudioBridgeClient: RemoteFunction matches
  StudioBridgeClient-->>ShipcheckTool: remoteFunctionMatches

  ShipcheckTool->>ShipcheckTool: filter weaponRemotes by weaponRemotePattern

  loop For each weaponRemote
    ShipcheckTool->>StudioBridgeClient: searchInstances(remoteName,script_content,root_path ServerScriptService)
    StudioBridgeClient->>RobloxStudio: search handler scripts
    RobloxStudio-->>StudioBridgeClient: handler matches
    StudioBridgeClient-->>ShipcheckTool: handlers

    alt No handlers found
      ShipcheckTool->>ShipcheckTool: add no_server_handler issue
    else Handlers found
      loop For each handler script
        ShipcheckTool->>StudioBridgeClient: getScriptSource(handler.path)
        StudioBridgeClient->>RobloxStudio: read script source
        RobloxStudio-->>StudioBridgeClient: script.source
        StudioBridgeClient-->>ShipcheckTool: script.source
        ShipcheckTool->>ShipcheckTool: append to combinedSource
      end

      alt check_type_validation enabled and no typeValidationPattern match
        ShipcheckTool->>ShipcheckTool: add missing_type_validation issue
      end

      alt check_rate_limiting enabled and no rateLimitingPattern match
        ShipcheckTool->>ShipcheckTool: add no_rate_limiting issue
      end
    end
  end

  ShipcheckTool->>ShipcheckTool: compute score from issues
  ShipcheckTool-->>ShipcheckTool: ResponseEnvelope(WeaponRemoteTrustResult)
Loading

Class diagram for new shooter Shipcheck tools

classDiagram

  class StudioBridgeClient {
    +port number
    +ping()
    +searchInstances(params)
    +getProperties(path)
    +getChildren(path, depth)
    +getScriptSource(path)
  }

  class ResponseEnvelope_T_ {
    +data T
    +metadata object
  }

  class SpawnClusteringIssue {
    +severity string
    +rule string
    +message string
    +spawn_path string
    +confidence string
  }

  class SpawnClusteringResult {
    +score number
    +spawn_count number
    +team_distribution Record_string_number_
    +avg_spread_studs number
    +issues SpawnClusteringIssue[]
  }

  class CombatContentMaturityIssue {
    +severity string
    +category string
    +rule string
    +message string
    +element_path string
    +confidence string
  }

  class CombatContentMaturityResult {
    +score number
    +scripts_scanned number
    +ui_elements_scanned number
    +findings_by_category Record_category_number_
    +issues CombatContentMaturityIssue[]
  }

  class WeaponRemoteTrustIssue {
    +severity string
    +remote_path string
    +rule string
    +message string
    +suggestion string
    +script_path string
  }

  class WeaponRemoteTrustResult {
    +score number
    +weapon_remotes_found number
    +issues WeaponRemoteTrustIssue[]
  }

  class runSpawnClustering {
    +runSpawnClustering(input)
  }

  class runCombatContentMaturity {
    +runCombatContentMaturity(input)
  }

  class runWeaponRemoteTrust {
    +runWeaponRemoteTrust(input)
  }

  class registerToolFn {
    +registerTool(config)
  }

  class ShooterSpawnClusteringTool {
    +name rbx_shooter_spawn_clustering
    +description string
  }

  class ShooterCombatContentMaturityTool {
    +name rbx_shooter_combat_content_maturity
    +description string
  }

  class ShooterWeaponRemoteTrustTool {
    +name rbx_shooter_weapon_remote_trust
    +description string
  }

  runSpawnClustering --> ResponseEnvelope_T_ : returns
  runSpawnClustering --> SpawnClusteringResult : payload
  runSpawnClustering --> StudioBridgeClient : uses

  runCombatContentMaturity --> ResponseEnvelope_T_ : returns
  runCombatContentMaturity --> CombatContentMaturityResult : payload
  runCombatContentMaturity --> StudioBridgeClient : uses

  runWeaponRemoteTrust --> ResponseEnvelope_T_ : returns
  runWeaponRemoteTrust --> WeaponRemoteTrustResult : payload
  runWeaponRemoteTrust --> StudioBridgeClient : uses

  ShooterSpawnClusteringTool ..> runSpawnClustering : handler
  ShooterCombatContentMaturityTool ..> runCombatContentMaturity : handler
  ShooterWeaponRemoteTrustTool ..> runWeaponRemoteTrust : handler

  registerToolFn ..> ShooterSpawnClusteringTool : registers
  registerToolFn ..> ShooterCombatContentMaturityTool : registers
  registerToolFn ..> ShooterWeaponRemoteTrustTool : registers
Loading

File-Level Changes

Change Details Files
Add shooter-focused Playtester scenario presets for weapon equip and respawn cycle flows.
  • Extend playtester CLI schema to allow two new shooter_* scenario presets.
  • Define shooter_weapon_equip scenario with StarterPack Tool existence, Handle presence, and basic weapon config value checks plus a final verification note.
  • Define shooter_respawn_cycle scenario that inspects Players respawn settings, SpawnLocation distribution, and CharacterAdded handlers, with a final verification note.
src/tools/playtester/playtester.ts
Introduce Shipcheck tool to detect spawn clustering, suspicious spawn placement, and team spawn imbalance in shooter games.
  • Define Zod schema for spawn clustering analysis with configurable min_spread_studs and optional team balance check.
  • Search for SpawnLocation instances via StudioBridgeClient, read key properties, and compute average spatial spread and team spawn distribution.
  • Emit heuristic issues for low average spread, highly imbalanced team spawn counts, and suspicious spawn heights, and compute a score based on issue count.
  • Register the tool as rbx_shooter_spawn_clustering with an appropriate description.
src/tools/shipcheck/genre/shooter/spawn-clustering.ts
Introduce Shipcheck tool to scan shooter games for combat content maturity concerns based on scripts and UI text.
  • Define schema and result types for combat content maturity scanning, including per-category finding counters.
  • Traverse the instance tree to find scripts and UI text elements, then scan their text against keyword lists for violence, weapon references, and social risk.
  • Create structured issues with category, severity, and manual_review confidence, and compute a score derived from the number of issues.
  • Register the tool as rbx_shooter_combat_content_maturity with a maturity/age-rating-focused description.
src/tools/shipcheck/genre/shooter/combat-content-maturity.ts
Introduce Shipcheck tool to audit weapon-related remote events/functions for server-side validation and rate limiting.
  • Define schema toggles for rate limiting and type validation checks, and result/issue structures for remote trust analysis.
  • Use StudioBridgeClient search APIs to find RemoteEvent/RemoteFunction instances whose paths match weapon-related patterns.
  • Search ServerScriptService scripts for handlers referencing each remote and flag cases with no server handler, missing type validation, or missing rate limiting using regex heuristics.
  • Register the tool as rbx_shooter_weapon_remote_trust with a description focused on server-side validation in shooter games.
src/tools/shipcheck/genre/shooter/weapon-remote-trust.ts
Enhance CI and review automation with SonarCloud analysis and Sourcery configuration.
  • Add a sonarcloud GitHub Actions job that runs on pushes and PRs from the same repo, installing dependencies, building, and running the SonarCloud scan using SONAR_TOKEN.
  • Introduce a .sourcery.yaml configuration enabling automated review suggestions while ignoring generated, dependency, plugin, JSON, and markdown files.
.github/workflows/ci.yml
.sourcery.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In runWeaponRemoteTrust, server handlers are only searched under game.ServerScriptService; consider either broadening the search root or making it configurable so projects that colocate server scripts elsewhere don’t produce misleading no_server_handler issues.
  • The hardcoded suspicious spawn height bounds (y < -10 or y > 1000) in spawn-clustering.ts could be exposed as schema options to better fit very tall/vertical maps or unusual level scales.
  • The keyword lists in combat-content-maturity.ts are currently fixed in-code; making them configurable (e.g., via tool input or a shared config) would let teams tune content maturity heuristics without code changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `runWeaponRemoteTrust`, server handlers are only searched under `game.ServerScriptService`; consider either broadening the search root or making it configurable so projects that colocate server scripts elsewhere don’t produce misleading `no_server_handler` issues.
- The hardcoded suspicious spawn height bounds (`y < -10` or `y > 1000`) in `spawn-clustering.ts` could be exposed as schema options to better fit very tall/vertical maps or unusual level scales.
- The keyword lists in `combat-content-maturity.ts` are currently fixed in-code; making them configurable (e.g., via tool input or a shared config) would let teams tune content maturity heuristics without code changes.

## Individual Comments

### Comment 1
<location path="src/tools/shipcheck/genre/shooter/spawn-clustering.ts" line_range="88-43" />
<code_context>
+  const client = new StudioBridgeClient({ port: input.studio_port });
+  await client.ping();
+
+  const matches = parseMatches(
+    await client.searchInstances({
+      query: "SpawnLocation",
+      search_type: "class",
+      case_sensitive: false,
+      max_results: 200,
+    }),
+  );
+
+  const issues: SpawnClusteringIssue[] = [];
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Handle failures from Studio search more explicitly instead of silently returning an empty match set.

`parseMatches` currently uses `safeParse(raw).data ?? []`, so any schema mismatch or API shape change from Studio becomes an empty list of spawns. That conflates “no spawns found” with “unexpected response shape” and can hide regressions. Please check `success` explicitly and either throw or surface/log a diagnostic when parsing fails so callers can distinguish these cases.

Suggested implementation:

```typescript
function parseMatches(raw: unknown) {
  const parsed = SpawnSearchResultSchema.safeParse(raw);

  if (!parsed.success) {
    // Surface a diagnostic so schema/API regressions are visible instead of
    // being conflated with "no spawns found".
    // This will cause callers to see a failure instead of silently
    // continuing with an empty match set.
    // eslint-disable-next-line no-console
    console.error(
      "[spawn-clustering] Unexpected Studio searchInstances response shape",
      parsed.error,
    );

    throw new Error(
      "[spawn-clustering] Failed to parse Studio searchInstances response",
    );
  }

  return parsed.data;
}

```

If the actual helper is named slightly differently or has a different return type, adjust the `SEARCH` block to match the current implementation of `parseMatches`.  
Also ensure that any higher-level caller of `runSpawnClustering` either allows this new error to propagate (so it is visible in logs/telemetry) or wraps it into the appropriate `ResponseEnvelope` error path, depending on how failures are normally surfaced in this toolchain.
</issue_to_address>

### Comment 2
<location path="src/tools/shipcheck/genre/shooter/weapon-remote-trust.ts" line_range="85-87" />
<code_context>
+    }),
+  );
+
+  const weaponRemotes = uniqueByPath([...remoteEventMatches, ...remoteFunctionMatches]).filter(
+    (match) => weaponRemotePattern.test(match.path),
+  );
+  const issues: WeaponRemoteTrustIssue[] = [];
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Filtering weapon remotes by full path can misclassify unrelated remotes whose ancestors match the pattern.

Because the regex is applied to `match.path`, any remote under a container with matching terms (e.g., `fire`, `damage`) will be treated as a weapon remote, even if the remote name itself isn’t. This risks noisy, inflated `weapon_remotes_found`. Consider matching against the remote’s name (or a specific property) instead, or restricting the regex to the final path segment.

```suggestion
  const weaponRemotes = uniqueByPath([...remoteEventMatches, ...remoteFunctionMatches]).filter(
    (match) => {
      const remoteName = match.path.split(".").pop() ?? match.path;
      return weaponRemotePattern.test(remoteName);
    },
  );
```
</issue_to_address>

### Comment 3
<location path=".github/workflows/ci.yml" line_range="31-35" />
<code_context>
       - name: Quality checks
         run: npm run check
+
+  sonarcloud:
+    runs-on: ubuntu-latest
+    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
+
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '20'
+          cache: 'npm'
+
+      - name: Install dependencies
+        run: npm ci
+
+      - name: Build
+        run: npm run build
+
+      - name: SonarCloud Scan
+        uses: SonarSource/sonarcloud-github-action@v5
+        env:
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider declaring explicit permissions for the SonarCloud job to avoid relying on GitHub Defaults.

This job inherits repository-level defaults because it doesn’t declare `permissions`. SonarCloud PR decoration usually needs only `contents: read` and `pull-requests: write`, so adding an explicit `permissions` block on this job will follow least-privilege and avoid breakage if repo defaults change.

```suggestion
  sonarcloud:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
    permissions:
      contents: read
      pull-requests: write

    steps:
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/tools/shipcheck/genre/shooter/spawn-clustering.ts
Comment thread src/tools/shipcheck/genre/shooter/weapon-remote-trust.ts
Comment thread .github/workflows/ci.yml
zaferdace and others added 3 commits March 29, 2026 21:50
- docs/shooter-checks.md: per-rule documentation with limitations
- examples/shooter-report.md: sample shooter audit report
- TESTING.md: add shooter genre test results (3 tools + 2 presets)
- README: add genre-specific checks section, update test table to 46 tools

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- weapon-remote-trust: match only remote name (not full path) to reduce
  false positives; add configurable server_script_root param
- spawn-clustering: expose min_height/max_height as schema options;
  throw on parse failure instead of silent empty result
- combat-content-maturity: add custom_keywords param to extend built-in
  keyword lists without code changes
- ci: add explicit permissions (contents:read, pull-requests:write)
  to sonarcloud job for least-privilege

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- lighting-preset: fix unused _ destructure and presetName variable
- sonarcloud job: add continue-on-error until project is configured
- prettier: auto-format shooter genre files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zaferdace zaferdace merged commit 10117ee into main Mar 29, 2026
2 of 3 checks passed
@zaferdace zaferdace deleted the feat/shooter-genre-mvp branch March 29, 2026 21:08
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