Validation and releases
Automated checks, how to run the full matrix, commit/review rules, release steps and Modrinth publishing
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.
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 --checkis 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).
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. |
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. |
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.
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.
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.
These are development rules. The repository does not currently declare branch protection or a required number of reviewers.
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:
- 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. - 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.
- Works out the versions. A stable release proposes the next version from the commits since the last one — a breaking change (
feat!:orBREAKING 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, since0.2.1-dev.12would sort below0.2.1and nobody on the dev channel would be offered it. - 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. - 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.
- 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
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.
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.
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.
Record and publish. Check each artifact's origin and Minecraft version, record the verified environments, known limitations and upgrade/rollback method (see release records), then submit the release notes and publish.
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/plugin/build/libs/socketsvc-proxy-<proxy>.jar; the same jar installs on Velocity or BungeeCord/Waterfall | <proxy>@proxy |
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.
Modrinth release script
Publishing to Modrinth uses 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.
# 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_TOKENinscripts/.env(gitignored) or the environment; required for everything except--dry-run. - Configuration:
scripts/modrinth.jsonholds 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 throughbuild_matrix.pyfor the selected versions and loaders, and Paper/proxy with:server:paper:buildandbuildAllProxy. - 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, 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 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
supportedand adjustdefaultif needed (see withdrawing a version or loader). - Verify protocol rollbacks using the mixed-version mechanism described in the voice protocol.