# Validation and releases

Automated checks, how to run the full matrix, commit/review rules, release steps and Modrinth publishing

Source: https://socketsvc.vercel.app/client/development/ci-release
## Automated checks

Each project's `build` runs its `check`, so these gates are enforced by a normal build:

| Check | What it actually verifies |
| --- | --- |
| `:client:fabric:test` | JUnit regressions for client/common: voice protocol, connection/reconnect, queues, audio state, translations, UI contracts, nametags, plus bytecode checks of the nametag hooks against the real game |
| `:client:fabric:mixinTest` | Starts Fabric Loader in a separate JVM with the real game classes: confirms SocketSVC is loaded, the EntityRenderState storage interface is injected, and the nametag renderer and LAN hosting Mixins apply |
| `:client:fabric:audioRegressionTest` | Loads and runs the current OS's native Opus/RNNoise JNI; synthetic regressions of the gate, jitter queue, limiter, echo handling and other existing audio flows |
| `:client:<loader>:verifyClientArtifact` | The final JAR's loader metadata, Minecraft/Java/loader constraints, class bytecode version, Mixin config and resources (icons/translations); Fabric additionally checks the WebSocket nested JAR and the JNI libraries for six platforms, NeoForge/Forge check that libraries are relocated and JNI is embedded with Jar-in-Jar |
| `:server:fabric:test` | server/common config: the mod's default config is identical to Paper's, YAML reading semantics |
| `:server:<loader>:verifyServerArtifact` | The server mod JAR's metadata, bundled libraries, no client or Paper content mixed in, bytecode |
| `:server:paper:test` | Relay regressions: authentication, routing, commands, hot reload, speaker API, queues and malformed input (the relay is shared with the server mods through `common/core`) |

For how to run these locally, see [common builds and tests](/client/development/setup#common-builds-and-tests).

### Test reports

| Report | Location |
| --- | --- |
| Client JUnit / Mixin | `client/fabric/build/<version>/reports/tests/test/`, `.../mixinTest/` |
| Server mod JUnit | `server/fabric/build/<version>/reports/tests/test/` |
| Paper | `server/paper/build/reports/tests/test/` |
| Synthetic audio results | Console output of `audioRegressionTest` |

### What to run for a change

- For documentation-only changes, checking the content, commands, links and `git diff --check` is enough; there is no need to rerun unrelated audio tests.
- When fixing a bug, prefer regression evidence that fails before the fix and passes after it. Do not add tests that merely restate the implementation.
- When synthetic audio tests pass but real audio is wrong, check device routing, capture/playback settings and do a real two-player test; synthetic tests do not cover every acoustic condition (see [audio validation](/client/development/audio-validation)).

## There is no CI

Nothing in this repository runs automatically: there are no GitHub Actions workflows and no CI
server. Every check and every release is a script in `scripts/`, run from a laptop, which is also
why there is no "CI-only" step that can drift away from what you run locally.

| Command | What it does |
| --- | --- |
| `python3 scripts/build_matrix.py` | Builds and tests every client and server mod loader for every supported Minecraft version. Add a version argument (`26.2`) to do just one. |
| `./gradlew :server:paper:build buildAllProxy -Ploaders=none` | Builds the Minecraft-independent JARs (Paper plugin, proxy relay). |
| `python3 scripts/release.py` | **The way to release.** A guided terminal tool: checks, versions, release notes, build, GitHub, Modrinth and Discord in one run — see [Releasing with release.py](#releasing-with-releasepy). |
| `python3 scripts/publish_release.py --dry-run` | Shows exactly what a publish would build and upload, without publishing. |
| `python3 scripts/publish_release.py` | The full matrix, then a `dev-<N>` prerelease on GitHub and a Discord announcement. See [updates](/client/updates). |
| `python3 scripts/publish_release.py --channel release` | The same with no version suffix, tagged `v<client.version>`. |

You need JDK 21 and 25, Python 3.9+ and git; publishing additionally needs the GitHub CLI
authenticated, and the Discord webhook in `scripts/.env` (verification needs no credentials at
all). The matrix must run serially in one working tree — Loom, ModDevGradle and ForgeGradle share
per-version state — so do not run two of them at once.

Because the matrix is read from the version catalog, adding a Minecraft version does not require
editing any build script.

<Callout type="warn" title="What the scripts do not do">
There is **no in-game client launch and no multiplayer acceptance** in any of this. NeoForge/Forge clients are only verified for compilation and packaging. Automated checks are not in-game acceptance; see the [per-version acceptance list](/client/development/versions#validation-levels).
</Callout>

## Commit and review

Commits follow Conventional Commits, e.g. `feat(client): add device selection`, `fix(audio): preserve speech endings`, `docs: document development workflow`. The subject focuses on the purpose; unrelated features, resource updates or cleanups go into separate commits. Breaking changes and rollbacks should include the reason and compatibility notes.

Before committing, check `git status`, `git diff`, `git diff --cached` and `git diff --check`. Commit only files related to the current requirement; never commit `build/`, `.gradle/`, IDE settings, game run data, credentials or real authentication details.

A PR description should include the problem and result, the affected modules/Minecraft versions, the test commands and their results, and any in-game acceptance still outstanding; protocol or config changes also describe mixed-version behaviour and rollback.

<Callout type="info">
These are development rules. The repository does not currently declare branch protection or a required number of reviewers.
</Callout>

## Publishing

### Releasing with release.py

`python3 scripts/release.py` does a whole release from one command, asking only what it has to, with arrow keys:

1. **Checks** that the tree is clean, on `main`, in step with origin (and offers to fast-forward), and that the GitHub CLI, the Discord webhook and the Modrinth token are all there.
2. **Asks what to publish:** a dev build, a stable release, or *plan only*, which shows everything a release would do and does none of it.
3. **Works out the versions.** A stable release proposes the next version from the commits since the last one — a breaking change (`feat!:` or `BREAKING CHANGE`) is minor before 1.0 and major after, a feature minor after 1.0, anything else a patch. Client, server and proxy move together; an addon (`cinema.version`) moves only if its own code changed. A dev build of a version that is already released moves to the next patch first, since `0.2.1-dev.12` would sort *below* `0.2.1` and nobody on the dev channel would be offered it.
4. **Writes the release notes** from those commits, grouped into New, Fixes, Performance and Changes, with a compare link — used for GitHub, Discord and Modrinth. Keep them, edit them in `$EDITOR`, or write your own.
5. **Steps to include:** quick tests before the long build, the GitHub release, Modrinth (stable only), Discord, and — after a stable release — moving the catalog on to the next version, so dev builds keep sorting above it.
6. **Runs it**, with every step live on screen and the last lines of Gradle's output under the one running. The version bump is only committed once the build has succeeded; a failed build puts the catalog back. The full output goes to `build/release-logs/`.

If a run stops partway — a failed upload, a closed laptop — running it again offers to **resume**, skipping every step that already finished.

`--channel dev|release` skips the first question, `--yes` takes every default without asking, and `--plan` only shows the plan. Standard library only: there is nothing to install.

### Before a release

<Steps>
<Step>

**Bump versions.** In `gradle/socketsvc.properties`, update the side(s) that actually changed: `client.version`, `server.version` (shared by Paper and the server mods) or `proxy.version`. New content must never overwrite a published JAR with the same name.

</Step>
<Step>

**Build the full matrix.** Run `buildAll`, or `python scripts/build_matrix.py` plus Paper/proxy. Confirm every matrix entry actually succeeded; a skipped or failed entry is not a release.

</Step>
<Step>

**Accept in-game.** With the test artifacts, test two-player voice, mute/deafen, joining/leaving servers and reconnecting, device switching, UI/keybinds, F1 HUD, and nametags with scoreboards/sneaking/occlusion. Native device compatibility must be accepted per OS. See the full [per-version checklist](/client/development/versions#validation-levels).

</Step>
<Step>

**Record and publish.** Check each artifact's origin and Minecraft version, record the verified environments, known limitations and upgrade/rollback method (see [release records](/client/development/contributing#version-retirement-and-release-records)), then submit the release notes and publish.

</Step>
</Steps>

### Artifacts

| Artifact | Install file | Modrinth version number |
| --- | --- | --- |
| Client (per version × loader) | `client/<loader>/build/<MC>/libs/socketsvc-client-<loader>-<client>+mc<MC>.jar` | `<client>+mc<MC>-<loader>@client` |
| Server mod (per version × loader) | `server/<loader>/build/<MC>/libs/socketsvc-server-<loader>-<server>+mc<MC>.jar` | `<server>+mc<MC>-<loader>@server` |
| Paper plugin | `server/paper/build/libs/socketsvc-server-paper-<server>.jar` | `<server>-paper@server` |
| Proxy relay plugin (optional, see [Proxy](/proxy)) | `proxy/plugin/build/libs/socketsvc-proxy-<proxy>.jar`; the same jar installs on Velocity or BungeeCord/Waterfall | `<proxy>@proxy` |

<Callout type="warn" title="Not release artifacts">
Do not install `-plain.jar` or `-slim.jar`; they are not complete artifacts. Do not use leftover artifacts in the old `client/build/`, `server/build/` or `proxy-plugin/build/` directories.
</Callout>

### Modrinth release script

Publishing to Modrinth uses [scripts/modrinth_release.py](https://github.com/HippoDev03/SocketSVC/blob/main/scripts/modrinth_release.py) (Python 3, standard library only). It builds and publishes every client loader, every server loader, the Paper plugin and the proxy plugin, each as its own Modrinth version whose version number carries its side.

```sh
# Show the release plan; builds and uploads nothing (no token needed)
python3 scripts/modrinth_release.py --dry-run

# Build everything and publish versions that do not exist yet
python3 scripts/modrinth_release.py --changelog-file CHANGELOG.md

# Only NeoForge clients for 26.2, reusing jars already built
python3 scripts/modrinth_release.py --side client --loaders neoforge --minecraft 26.2 --skip-build
```

| Option | Effect |
| --- | --- |
| `--dry-run` | Print the release plan without building or uploading; no token needed (if a token is available, versions already on Modrinth are shown as skipped) |
| `--skip-build` | Reuse jars already under `*/build/`; the script stops if an artifact is missing |
| `--side client\|server\|paper\|proxy` | Release only these sides (repeatable; default: all) |
| `--loaders` | Comma-separated mod loaders (`fabric`, `neoforge`, `forge`; default: all) |
| `--minecraft` | Comma-separated Minecraft versions for client/server mods (default: all supported); unsupported versions are rejected |
| `--changelog "text"` / `--changelog-file FILE` | Changelog text attached to every version |
| `--version-type release\|beta\|alpha` | Force every artifact to this type (default: `release`, or `beta` for beta loaders) |
| `--replace` | Delete and re-publish versions whose number already exists, instead of skipping them; this loses that version's download stats |

How it behaves:

- **Token**: `MODRINTH_TOKEN` in `scripts/.env` (gitignored) or the environment; required for everything except `--dry-run`.
- **Configuration**: `scripts/modrinth.json` holds the project id and slug, the version name prefix, and the loader tags and dependencies per side and loader.
- **Building**: unless `--skip-build`, client/server mods are built through `build_matrix.py` for the selected versions and loaders, and Paper/proxy with `:server:paper:build` and `buildAllProxy`.
- **Skipping existing versions**: a Modrinth version with the same version number is skipped by default, so re-running after bumping only one side's version publishes only that side.
- **Beta loaders**: artifacts built against a beta loader (a NeoForge version containing `-beta`) are published as beta automatically.
- **Version number limit**: Modrinth limits version numbers to 32 characters from a restricted character set; the script stops before uploading if a number breaks that, in which case shorten the side version in `gradle/socketsvc.properties`.

What each side is published with (from `scripts/modrinth.json` and the script):

| Side | Modrinth loaders | Game versions | Dependencies | Environment |
| --- | --- | --- | --- | --- |
| Client, Fabric | `fabric`, `quilt` | That Minecraft version | Fabric API (required), Mod Menu (optional) | client only |
| Client, NeoForge / Forge | `neoforge` / `forge` | That Minecraft version | none | client only |
| Server mod, Fabric | `fabric`, `quilt` | That Minecraft version | Fabric API (required) | server only |
| Server mod, NeoForge / Forge | `neoforge` / `forge` | That Minecraft version | none | server only |
| Paper plugin | `paper`, `purpur`, `folia` | All supported versions | none | server only |
| Proxy plugin | `velocity`, `bungeecord`, `waterfall` | All supported versions | none | server only, not featured |

### Store listing

The Modrinth project page text lives in [the Modrinth listing](https://github.com/HippoDev03/SocketSVC/blob/main/scripts/modrinth_listing.md), beside the release script: a short description (under the `Short Desc` marker) and the long description (under `Long Desc`), covering why WebSocket instead of UDP, voice chat features, the spatial speaker API, requirements, installation, server configuration highlights and servers using SocketSVC. The release script only creates versions and does not update the project description, so edits to it have to be applied to the Modrinth project page separately. Keep it consistent with the [overview](/client) when features or requirements change.

## Rollback

- Roll back to a known-good artifact and keep player settings.
- For source rollback, prefer a revert with a stated reason; avoid rewriting shared branch history.
- When withdrawing support for a Minecraft version, update `supported` and adjust `default` if needed (see [withdrawing a version or loader](/client/development/versions#withdrawing-a-version-or-loader)).
- Verify protocol rollbacks using the mixed-version mechanism described in the [voice protocol](/server/voice-protocol).