Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: rpc/go.mod
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run cross-language and process acceptance
run: |
python3 -m venv "$RUNNER_TEMP/match-platform-python"
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 3.0.0

- Align the Match Platform release major with Client Protocol V3.
- Publish a canonical `VERSION` and executable version compatibility contract.
- Supersede the historical `v0.1.0` tag without changing the V3 wire semantics.

## 0.1.0

- Extract the generic Match Platform history, core, operations, RPC runtime,
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Engine-neutral contracts and runtimes for authoritative multiplayer games.

Current release line: **Match Platform 3.x**, paired with **Client Protocol V3**.
The canonical full release version is stored in [`VERSION`](VERSION).

This repository owns:

- Client Protocol V3 envelopes and the Godot platform core;
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0
3.0.0
20 changes: 13 additions & 7 deletions VERSIONING.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# Versioning policy

Repository releases use SemVer.
Repository releases use SemVer, and the release major MUST equal the supported
Client Protocol major. Client Protocol V3 is therefore released as Match
Platform `3.x.y`; there is no separate `0.x` product-version namespace.

- Patch: fixes that preserve Client Protocol V3, Adapter RPC v1 and SDK behavior.
- Minor: backward-compatible capabilities, SDKs, runtimes or operations.
- Major: an incompatible public contract or artifact-layout change.
- Patch (`3.0.x`): fixes that preserve Client Protocol V3, Adapter RPC v1 and SDK behavior.
- Minor (`3.x.0`): backward-compatible capabilities, SDKs, runtimes or operations.
- Major (`4.0.0`): Client Protocol V4 and its incompatible public contract. An
artifact-layout break that requires all consumers to migrate also requires the
next protocol/repository major, even if the envelope changes only minimally.

Client Protocol, Adapter RPC, adapter package, codec and content versions remain
independent. The historical protobuf package name
Adapter RPC, adapter package, codec and content versions remain independent.
The historical protobuf package name
`hersir.adapter.rpc.v1` is retained as a frozen wire ABI; it does not imply a
dependency on the downstream game.

Release tags are immutable. Consumers pin both a tag for human review and its
full commit SHA for resolution.
full commit SHA for resolution. A consumer MUST verify that the tag major, the
`VERSION` file, `MatchPlatformV3.PLATFORM_VERSION_MAJOR`, and
`MatchPlatformV3.ENVELOPE_VERSION` agree.
4 changes: 2 additions & 2 deletions docs/adr/0003-match-platform-v3-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Every V3 message is one dictionary carrying `pv` (=3) and `t` (type). Direction
server→client. Fields below are all **platform** fields; none names a game concept.

```text
hello { pv, protocol_versions, game_id, game_version, content_hash, codecs }
welcome { pv, selected_protocol, game_id, adapter_version, selected_codec, tick_rate, capabilities }
hello { pv, protocol_versions, platform_version?, game_id, game_version, content_hash, codecs }
welcome { pv, selected_protocol, platform_version?, game_id, adapter_version, selected_codec, tick_rate, capabilities }
join { pv, match_selector, role, requested_slot?, auth_context }
command { pv, match_id, seq, expected_tick, codec_id, payload:opaque }
state { pv, match_id, tick, base_tick, seq, codec_id, payload:opaque }
Expand Down
16 changes: 16 additions & 0 deletions platform/match_platform_v3.gd
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ extends RefCounted
## game or codec is not.
const ENVELOPE_VERSION := 3

## Repository release version. Its SemVer major is deliberately identical to
## ENVELOPE_VERSION: a Client Protocol V3 frontend consumes Match Platform 3.x.
## Minor and patch releases must remain compatible with the same V3 envelope.
const PLATFORM_VERSION := "3.0.0"
const PLATFORM_VERSION_MAJOR := 3
const WEBSOCKET_SUBPROTOCOL := "match-platform.v%d" % PLATFORM_VERSION_MAJOR

## True when a Match Platform SemVer belongs to this protocol generation.
## This intentionally checks the major only; compatible fixes and capabilities
## may ship as 3.x without forcing coordinated client/server deployment.
static func supports_platform_version(version: String) -> bool:
var parts := version.trim_prefix("v").split(".")
return parts.size() == 3 and parts[0].is_valid_int() \
and parts[1].is_valid_int() and parts[2].is_valid_int() \
and int(parts[0]) == PLATFORM_VERSION_MAJOR

## Maximum decoded envelope size the core will accept, payload included. The core
## bounds the payload by bytes only; it never parses inside it. Mirrors the legacy
## v1/v2 wire ceiling so a V3 frame rides the same transport limits during
Expand Down
6 changes: 6 additions & 0 deletions platform/platform_adapter_registry.gd
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ func resolve_hello(hello: Dictionary) -> Dictionary:
var selected_protocol := V3.negotiate_protocol(client_protocols, SUPPORTED_PROTOCOLS)
if selected_protocol == 0:
return _refuse(V3.REJECT_UNSUPPORTED_PROTOCOL, "no shared platform protocol version")
var client_platform_version := String(hello.get("platform_version", ""))
if not client_platform_version.is_empty() \
and not V3.supports_platform_version(client_platform_version):
return _refuse(V3.REJECT_UNSUPPORTED_PROTOCOL,
"client Match Platform major does not match Client Protocol V%d" % selected_protocol)

var game_id := String(hello.get("game_id", ""))
if not _packages.has(game_id):
Expand Down Expand Up @@ -184,6 +189,7 @@ func welcome_for(hello: Dictionary, capabilities: Dictionary = {}) -> Dictionary
return V3.reject(StringName(resolved.get("code", V3.REJECT_MALFORMED_ENVELOPE)), String(resolved.get("detail", "")))
return V3.envelope(V3.WELCOME, {
"selected_protocol": int(resolved.selected_protocol),
"platform_version": V3.PLATFORM_VERSION,
"game_id": String(resolved.game_id),
"adapter_version": String(resolved.adapter_version),
"selected_codec": String(resolved.selected_codec),
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/v3_envelope_fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"pv": 3,
"t": "hello",
"protocol_versions": [3],
"platform_version": "3.0.0",
"game_id": "gridwars",
"game_version": "1.4.0",
"content_hash": "sha256:9f2c0a",
Expand All @@ -24,6 +25,7 @@
"pv": 3,
"t": "welcome",
"selected_protocol": 3,
"platform_version": "3.0.0",
"game_id": "gridwars",
"adapter_version": "1.4.0",
"selected_codec": "gridwars.binary.1",
Expand Down
6 changes: 6 additions & 0 deletions tests/platform_core_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ func _hello(overrides: Dictionary = {}) -> Dictionary:
var packet := {
"pv": V3.ENVELOPE_VERSION, "t": String(V3.HELLO),
"protocol_versions": [V3.ENVELOPE_VERSION], "game_id": "gridwars",
"platform_version": V3.PLATFORM_VERSION,
"game_version": "1.0.0", "content_hash": GridWarsAdapter.CONTENT_HASH,
"codecs": ["gw-binary", "gw-json"],
}
Expand Down Expand Up @@ -314,6 +315,9 @@ func _test_pre_seat_identity_gate() -> void:
var outcome := registry.resolve_hello(cases[expected])
_check(not bool(outcome.get("ok", false)) and String(outcome.get("code", "")) == String(expected),
"hello is refused with '%s'" % expected)
var incompatible_platform := registry.resolve_hello(_hello({"platform_version": "4.0.0"}))
_check(String(incompatible_platform.get("code", "")) == String(V3.REJECT_UNSUPPORTED_PROTOCOL),
"hello from a different Match Platform major is refused pre-seat")

# Structural failures are still structural: a hello missing a mandatory identity
# field never reaches negotiation.
Expand All @@ -333,6 +337,8 @@ func _test_welcome_negotiation() -> void:
_check(bool(V3.validate_envelope(welcome, V3.SERVER_MESSAGES).get("ok", false)),
"welcome_for builds a contract-valid welcome envelope")
_check(String(welcome.get("adapter_version", "")) == "0.1.0", "welcome carries the adapter version")
_check(String(welcome.get("platform_version", "")) == V3.PLATFORM_VERSION,
"welcome identifies the server Match Platform release")
_check(bool(welcome.get("capabilities", {}).get("resync", false)),
"capabilities are platform-declared, not game-declared")
var refusal := registry.welcome_for(_hello({"game_id": "nosuchgame"}))
Expand Down
12 changes: 12 additions & 0 deletions tests/v3_contract_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func _run() -> void:
quit(1)
return
_test_valid_envelopes(fixtures)
_test_release_version()
_test_invalid_envelopes(fixtures)
_test_negotiation(fixtures)
_test_delivery_classes(fixtures)
Expand All @@ -58,6 +59,17 @@ func _run() -> void:
print(" - %s" % failure)
quit(1)

func _test_release_version() -> void:
var release_version := FileAccess.get_file_as_string("res://VERSION").strip_edges()
_check(release_version == V3.PLATFORM_VERSION,
"VERSION matches the executable platform version")
_check(V3.PLATFORM_VERSION_MAJOR == V3.ENVELOPE_VERSION,
"Match Platform major matches the Client Protocol major")
_check(V3.supports_platform_version("3.0.0"), "same-major platform release is compatible")
_check(V3.supports_platform_version("v3.9.7"), "v-prefixed same-major release is compatible")
_check(not V3.supports_platform_version("4.0.0"), "next-major platform release is incompatible")
_check(not V3.supports_platform_version("3.0"), "non-SemVer platform release is invalid")

func _load_fixtures() -> Dictionary:
var text := FileAccess.get_file_as_string(FIXTURES_PATH)
var parsed: Variant = JSON.parse_string(text)
Expand Down
17 changes: 11 additions & 6 deletions tools/build_release_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import argparse
import hashlib
import json
import re
import subprocess
import tarfile
import tempfile
Expand All @@ -22,18 +23,22 @@ def main() -> None:
parser.add_argument("--version", required=True)
args = parser.parse_args()

version = re.sub(r"[^A-Za-z0-9._-]+", "-", args.version).strip(".-")
if not version:
raise SystemExit("version must contain at least one filename-safe character")

root = Path(__file__).resolve().parents[1]
commit = subprocess.check_output(
["git", "rev-parse", "HEAD"], cwd=root, text=True
).strip()
args.output.mkdir(parents=True, exist_ok=True)
archive = args.output / f"match-platform-{args.version}.tar.gz"
archive = args.output / f"match-platform-{version}.tar.gz"

with tempfile.TemporaryDirectory() as temporary:
source_tar = Path(temporary) / "source.tar"
with source_tar.open("wb") as stream:
subprocess.run(
["git", "archive", "--format=tar", f"--prefix=match-platform-{args.version}/", "HEAD"],
["git", "archive", "--format=tar", f"--prefix=match-platform-{version}/", "HEAD"],
cwd=root,
stdout=stream,
check=True,
Expand All @@ -53,16 +58,16 @@ def main() -> None:
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": f"match-platform-{args.version}",
"documentNamespace": f"https://github.com/chimerakang/match-platform/releases/{args.version}/{commit}",
"name": f"match-platform-{version}",
"documentNamespace": f"https://github.com/chimerakang/match-platform/releases/{version}/{commit}",
"creationInfo": {
"creators": ["Tool: match-platform-release-builder"],
},
"packages": [
{
"name": "match-platform",
"SPDXID": "SPDXRef-Package",
"versionInfo": args.version,
"versionInfo": version,
"downloadLocation": "NOASSERTION",
"filesAnalyzed": False,
"checksums": [
Expand All @@ -81,7 +86,7 @@ def main() -> None:
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/chimerakang/match-platform/.github/workflows/ci.yml",
"externalParameters": {"version": args.version},
"externalParameters": {"version": version},
"resolvedDependencies": [
{
"uri": "git+https://github.com/chimerakang/match-platform",
Expand Down
Loading