Subspace Infinity is a single-maintainer, pre-alpha hobby project. Contributions are welcome — code, documentation, bug reports, balance feedback — and are reviewed on a best-effort basis. Reviews typically happen within 2–3 days; if your PR sits longer than that, ping in Discord.
By submitting a pull request, you agree your contribution is licensed under the BSD-3-Clause license that covers the rest of the project. Note that LICENSE.md covers code and Subspace Infinity's own assets only — see THIRD-PARTY-NOTICES.md for material with separate provenance (Subspace/Continuum game files, community maps, attributed textures).
This document covers process and conventions — how to file an issue, how to scope a PR, what rules your code must follow, and what verification a PR needs before merge.
It does not cover setup, build commands, or architecture. For those:
| You want to… | Read |
|---|---|
| Set up your dev environment | docs/setup-guide.md |
| Build and run the project | BUILDING.md |
| Understand the codebase | docs/developer-guide.md |
| Look up a common command | docs/quick-reference.md |
| Learn the project's domain language | CONTEXT.md |
| Read the canonical project rules | CLAUDE.md |
Issues live on the GitHub issues page.
Before filing:
- Check for duplicates.
- One issue per bug or feature.
- Stay on topic — bugs, features, and balance feedback only. General questions belong in Discord.
For feature requests: the gate is "does it make the game more fun?" This is a game, not a simulator. Changes that add complexity for the sake of realism won't be accepted.
| Kind | Meaning |
|---|---|
bug |
Game not behaving as intended |
enhancement |
New engine or gameplay functionality |
docs |
Documentation improvements |
balance |
Gameplay balance issues |
mechanics |
Game mechanics changes |
content |
New content (no code changes) |
| State | Meaning |
|---|---|
needs-info |
Maintainer needs more information from the reporter |
wontfix |
Won't be implemented |
If you're new and unsure where to dig in, easy starting points:
- Documentation fixes — typos, broken links, unclear sections in any
*.mdfile. - Spotless violations — run
./gradlew spotlessCheckand fix anything it flags. - Groovy presets — fill in or extend an arena under infinity/zone/conf/ or infinity/zone/arenas/.
- Failing or skipped tests —
./gradlew testand look for things to investigate.
If none of that fits and you'd like a maintainer's blessing on a direction first, ping in Discord — happy to point you at something.
These rules are mirrored from CLAUDE.md. CLAUDE.md is canonical — if this list disagrees with it, CLAUDE.md wins.
| Rule | Why | Canonical |
|---|---|---|
Use final for method parameters |
Project convention; catches accidental reassignment | CLAUDE.md §Always-on Rules #1 |
SPDX-only BSD-3-Clause license header on every source file |
Licensing; copy from any existing file | CLAUDE.md §Always-on Rules #2 |
| Tuning knobs go in Groovy, not Java | Gameplay balance shouldn't require a recompile | CLAUDE.md §Always-on Rules #4 |
Layer boundaries: api/ is data + interfaces only; client observes, server owns |
Keeps the api ↔ server ↔ client split honest; enforced by LayerDependencyTest | .claude/rules/ |
When you touch the corresponding directory, the rules below apply. The full text lives in .claude/rules/ — read the matching file before editing.
| If you touch… | Rule |
|---|---|
api/src/main/java/infinity/es/** |
components.md — immutable, no-arg constructor, register cross-wire components |
api/src/** |
api-contracts.md — data + interfaces only; no deps on server/client |
client/** and *AppState files |
client-read-only.md — client observes, server owns; writes via RMI |
infinity/systems/** |
systems.md — logic-in-systems; no duplicate component producers |
infinity/** (Java) |
entity-sets.md — release EntitySet in terminate() |
infinity/** (Java) |
world-coordinates.md — use TileId APIs, not * 1024 |
api/src/main/java/infinity/config/**, api/src/main/java/infinity/es/ship/** |
config-pattern.md — template (*Config) vs instance (components); spawn projects template → component |
infinity/systems/ship/applier/** |
prize-applier.md — match canonical Subspace prize semantics from REFERENCE.md |
| Any TTL/decay code | decay-ttl.md — Decay is the only TTL mechanism |
- Run Spotless before submitting:
./gradlew spotlessApply. The build will fail if formatting is wrong. - License header on every source file. SPDX-only —
// SPDX-License-Identifier: BSD-3-Clausefollowed by// Copyright (c) 2018-2026 Asser Fahrenholz. Full text is in LICENSE.md. finalon method parameters. Always.- Comments: default to none. Only write a comment when the why is non-obvious — a hidden constraint, a subtle invariant, a workaround for a specific bug. Don't explain what well-named code already says.
- Fork the repository and push your branch to your fork.
- Open a PR against
infinity— this is the project's default branch, notmain. - One concern per PR. Don't combine unrelated changes. If a refactor and a feature are tangled, split them.
- Open as Draft if you want early feedback or the PR isn't ready for review.
Conventional Commits style is recommended but not required. Format: type(scope): subject. Examples from recent history:
docs(readme): rewrite as best-practice README
test(harness): extend spawn-projection harness to prize + projectile pillars
docs(prd): add github-pages do-over PRD
Common types: feat, fix, docs, test, refactor, chore, build.
If your PR addresses an issue, link it in the description (Fixes #123). Required only for non-trivial PRs — typo and small docs fixes don't need an issue first.
Aim for a single concern and a reviewable diff. PRs that are too large to review will be asked to split before merge — this is a maintainer judgment call, not a hard line count.
Automated tests are preferred. If your change is testable, add a test under the relevant module's src/test/. Run ./gradlew build to make sure existing tests still pass.
When automated testing isn't feasible (most gameplay-bearing changes — there's no automated gameplay test harness yet), launch the game and verify your change manually, then include a short "what I tested" note in the PR description: what scenario you exercised and what you observed. Pure refactors with no behavior change can self-attest.
-
./gradlew buildpasses locally -
./gradlew spotlessApplyrun, no diff left over - SPDX-only
BSD-3-Clauseheader on any new source files - Followed the path-scoped rules in .claude/rules/ for files you touched
- One concern per PR
- Linked an issue if applicable
- Automated test added, or a "what I tested" note in the PR description
- Targeted the
infinitybranch
Join us on Discord.