docs(integration): record SpaceGame pilot checkpoint (#21)
quality-gate / quality (push) Failing after 1m26s
quality-gate / container (push) Has been skipped

This commit is contained in:
KyuubiYoru
2026-07-16 19:18:03 +02:00
parent 7fb85059fb
commit ebb5eb617c
8 changed files with 217 additions and 6 deletions
+4
View File
@@ -119,6 +119,10 @@ versioning, and secure rollout seams are in the
The always-on three-party scenarios, optional Linux namespace topology, and The always-on three-party scenarios, optional Linux namespace topology, and
simulation limits are documented in the simulation limits are documented in the
[deterministic topology harness](docs/integration/topology-harness.md). [deterministic topology harness](docs/integration/topology-harness.md).
The current consumer evidence and the still-open public-package, Godot-process,
fallback, reconnect, Linux, and external-NAT gates are tracked in the
[SpaceGame consumer pilot](docs/integration/spacegame-pilot.md).
## Development ## Development
+5 -3
View File
@@ -39,14 +39,16 @@
"GameId": "space-game", "GameId": "space-game",
"EnvironmentId": "smoke", "EnvironmentId": "smoke",
"Enabled": true, "Enabled": true,
"ProtocolVersions": [1], "ProtocolVersions": [1, 2],
"Regions": ["local"], "Regions": ["local"],
"VisibilityModes": ["Public"], "VisibilityModes": ["Public"],
"PublisherTrustModes": ["ManagedDedicated"], "PublisherTrustModes": ["ManagedDedicated"],
"MetadataValueMaxBytes": {}, "MetadataValueMaxBytes": {
"mode": 32
},
"RequiredMetadataKeys": [], "RequiredMetadataKeys": [],
"MetadataMaxBytes": 512, "MetadataMaxBytes": 512,
"MetadataMaxKeys": 0, "MetadataMaxKeys": 1,
"MaxListingsPerPrincipal": 10, "MaxListingsPerPrincipal": 10,
"MaxAnonymousListingsPerAddress": 0, "MaxAnonymousListingsPerAddress": 0,
"MaxActiveJoinAttempts": 100, "MaxActiveJoinAttempts": 100,
+62
View File
@@ -0,0 +1,62 @@
{
"schemaVersion": "1.0",
"recordedAt": "2026-07-16",
"issue": 21,
"consumerIssue": "Kyuubi/SpaceGame#3",
"result": "checkpoint-pass-with-open-gates",
"rendezvousCommit": "7fb85059fb7003ca0984ebd0cc9541218d33ae93",
"consumerCommit": "86424ebde69a7eeae110106ab5f84c68f04af89b",
"packages": {
"FinalFactory.Rendezvous.Client": {
"version": "1.0.0",
"source": "local-candidate",
"sha256": "606711e4961867ca8fe95fde973aa85f2b6e7483cafe4867ca8daa918aa78b6b"
},
"FinalFactory.Rendezvous.Contracts": {
"version": "1.0.0",
"source": "local-candidate",
"sha256": "394acf0a1419690829ff38eb214b1833cf64edbc85bbb7182345bfe77b8ec1f0"
},
"LiteNetLib": {
"version": "2.1.4"
}
},
"localRun": {
"processes": ["Rendezvous", "SpaceGame host pilot", "SpaceGame client pilot"],
"typedOutcome": "Connected",
"gameAdmission": "Accepted",
"directGameplay": true,
"directInputs": 1,
"directSnapshots": 1,
"rendezvousGameplayPayloads": 0,
"deregistered": true
},
"negativePaths": {
"incompatibleProtocol": "proven",
"staleHostPresence": "proven",
"punchTimeout": "proven",
"invalidAdmission": "unit-and-protocol-boundary-proven",
"capacity": "regression-tested",
"fallbackConnection": "open",
"reconnect": "open"
},
"verification": {
"debugBuild": "passed",
"releaseBuild": "passed",
"debugTests": { "passed": 29, "failed": 0 },
"releaseTests": { "passed": 29, "failed": 0 },
"format": "passed",
"shellcheck": "passed",
"twoProcessHarness": "passed",
"adversarialReview": "passed-after-fixes"
},
"openGates": [
"public-package-restore",
"actual-godot-process-integration",
"dedicated-fallback-connection",
"disconnect-reconnect",
"remote-linux-dedicated",
"representative-external-nat",
"spacegame-imgui-native-runtime-dependency"
]
}
+5
View File
@@ -70,6 +70,7 @@ thread and do not also call `NetManager.PollEvents()` during that period.
```csharp ```csharp
RendezvousNetListener networkEvents = new(); RendezvousNetListener networkEvents = new();
NetManager gameplayNetwork = networkEvents.CreateManager(); NetManager gameplayNetwork = networkEvents.CreateManager();
gameplayNetwork.ChannelsCount = 3; // set the game's required count before Start
if (!gameplayNetwork.Start(gameplayPort)) if (!gameplayNetwork.Start(gameplayPort))
{ {
throw new InvalidOperationException("Gameplay UDP socket could not start."); throw new InvalidOperationException("Gameplay UDP socket could not start.");
@@ -85,6 +86,10 @@ using RendezvousHostCoordinator host = new(
host.Poll(); // call each game frame while this coordinator owns polling host.Poll(); // call each game frame while this coordinator owns polling
``` ```
LiteNetLib defaults to one QoS channel. Set `ChannelsCount` before `Start` when
the game protocol uses additional channels; both peers must configure the same
count. Rendezvous does not choose, remap, or reserve a gameplay channel.
Register normal game callbacks on `networkEvents.GameplayEvents`. Rendezvous Register normal game callbacks on `networkEvents.GameplayEvents`. Rendezvous
reserves only its authenticated direct requests and forwards other callbacks. reserves only its authenticated direct requests and forwards other callbacks.
The same socket sends host presence, punches through the mediator, establishes The same socket sends host presence, punches through the mediator, establishes
+107
View File
@@ -0,0 +1,107 @@
# SpaceGame consumer pilot
Tracking: Rendezvous #21 and SpaceGame #3.
The first SpaceGame checkpoint proves that the v1 client boundary can establish
authenticated direct LiteNetLib traffic without taking ownership of the game's
protocol, admission, player identity, entity identity, capacity, or gameplay
payloads. It does not yet pass the complete consumer-pilot gate. The public
package restore, actual Godot-process integration, connected fallback transport,
remote Linux dedicated run, representative external NAT run, and reconnect path
remain required before #21 can close.
## Pinned checkpoint
| Input | Value |
| --- | --- |
| Rendezvous source | `7fb85059fb7003ca0984ebd0cc9541218d33ae93` plus the #21 compatibility changes |
| SpaceGame source | `86424ebde69a7eeae110106ab5f84c68f04af89b` |
| Client package | `FinalFactory.Rendezvous.Client` `1.0.0` |
| Contracts package | `FinalFactory.Rendezvous.Contracts` `1.0.0` |
| LiteNetLib | `2.1.4` |
| HTTP, UDP, ticket contracts | `1` |
| SpaceGame gameplay protocol | `2` |
At the checkpoint date, the Final Factory Gitea NuGet service was reachable but
both `FinalFactory.Rendezvous.*` `1.0.0` registrations returned HTTP 404. The run
therefore restored locally built candidate packages with the hashes recorded in
[`spacegame.json`](../evidence/consumers/spacegame.json). This proves candidate
compatibility, not immutable registry publication. The release package restore
must be repeated from the public feed.
## Proven local path
The SpaceGame host and client each create one caller-owned `NetManager`, set its
three gameplay QoS channels before `Start`, and give the same manager and
`RendezvousNetListener` to the coordinator. Rendezvous authenticates discovery,
join authorization, host presence, mediation, and connection outcome reporting.
After traversal, SpaceGame performs a separate audience-bound admission exchange
on its own reliable command channel. A trusted game-auth boundary mints the
opaque assertion; the player process never receives the signing key.
The authoritative host rejects expired, replayed, incorrectly signed,
wrong-listing, duplicate-player, over-capacity, identity-mismatched,
out-of-sequence, and over-rate traffic. It assigns a canonical game entity ID
only after admission. The player ID, entity ID, listing ID, join-attempt ID, and
LiteNetLib peer ID remain distinct values.
The bounded two-process harness observed:
- host publication and lease maintenance;
- browser compatibility filtering and join authorization;
- typed traversal outcome `Connected`;
- successful audience-bound game admission;
- one reliable ordered input and one sequenced state snapshot on the direct
gameplay socket;
- zero SpaceGame gameplay payloads handled by Rendezvous; and
- successful host deregistration.
Both Debug and Release builds passed. Both Debug and Release test runs passed 29
tests with zero failures. The focused formatter, shell checker, clean
candidate-package restore, and adversarial branch review also passed.
## Failure evidence
| Path | Evidence | Status |
| --- | --- | --- |
| Incompatible protocol | protocol `999` returns no compatible listing and starts no traversal | Proven |
| Stale/no host presence | typed `NoHostPresence/RendezvousService/HostPresence/Mediation` | Proven |
| Traversal timeout | non-listening mediator produces typed `PunchTimedOut/LocalTraversal/NatTraversal/NatTraversal` | Proven |
| Rejected game admission | invalid signature and wrong audience deny gameplay; expiry and replay are regression-tested | Proven at protocol/unit boundary |
| Capacity and duplicate player | game-owned roster rejects both and publishes current capacity | Regression-tested |
| Configured fallback | policy excludes authorization, compatibility, and capacity failures | Policy only; transport path open |
| Disconnect | direct session ends and host deregisters | Proven for checkpoint shutdown |
| Reconnect | a second authenticated session after disconnect | Open |
## Rendezvous-side compatibility fixes
The pilot found generic integration gaps and keeps their fixes in this
repository:
- the local production-shaped smoke tenant accepts SpaceGame gameplay protocol
`2` and the bounded `mode` metadata key;
- SDK guidance requires games using multiple LiteNetLib QoS channels to set
`ChannelsCount` before `Start` and states that Rendezvous reserves no gameplay
channel; and
- the local credential helper rejects any signing-key file with group or other
permissions, in addition to its ownership, symlink, and hard-link checks.
Documentation contract tests cover these generic requirements.
## Remaining acceptance gates
Do not mark #21 passed until all of these have direct evidence:
1. restore the exact immutable `1.0.0` packages from the public Gitea feed;
2. drive the host and client through actual Godot game processes, not only the
console pilot;
3. implement and exercise the configured dedicated fallback connection with the
same game admission and identity rules;
4. prove disconnect followed by a fresh authenticated reconnect;
5. run a remote client against a Linux dedicated host and capture normal
termination/drain behavior;
6. run representative external NAT canaries and record the network topology and
typed outcome; and
7. clear the current SpaceGame Linux runtime blocker: its pre-existing ImGui
GDExtension native library is absent. A headless process exit code of zero
while that load error is present is not runtime proof.
+2 -2
View File
@@ -42,8 +42,8 @@ if stat.S_ISLNK(parent.st_mode) or not stat.S_ISDIR(parent.st_mode):
raise SystemExit(f"Local Compose secret directory must be a non-symlink directory: {parent_path}") raise SystemExit(f"Local Compose secret directory must be a non-symlink directory: {parent_path}")
if parent.st_uid != os.geteuid() or parent.st_mode & 0o077: if parent.st_uid != os.geteuid() or parent.st_mode & 0o077:
raise SystemExit(f"Local Compose secret directory must be owned by this user with mode 0700: {parent_path}") raise SystemExit(f"Local Compose secret directory must be owned by this user with mode 0700: {parent_path}")
if metadata.st_uid != os.geteuid() or metadata.st_mode & 0o022 or metadata.st_nlink != 1: if metadata.st_uid != os.geteuid() or metadata.st_mode & 0o077 or metadata.st_nlink != 1:
raise SystemExit(f"Local Compose smoke key must be owned by this user, single-linked, and not group/world writable: {key_path}") raise SystemExit(f"Local Compose smoke key must be owned by this user, single-linked, and private to its owner: {key_path}")
with open(key_path, "rb") as key_file: with open(key_path, "rb") as key_file:
key = key_file.read(33) key = key_file.read(33)
@@ -67,12 +67,17 @@ factory does not open a socket, and synchronized events must remain enabled:
```csharp ```csharp
RendezvousNetListener networkEvents = new(); RendezvousNetListener networkEvents = new();
NetManager gameplayNetManager = networkEvents.CreateManager(); NetManager gameplayNetManager = networkEvents.CreateManager();
gameplayNetManager.ChannelsCount = 3; // example: configure the game protocol first
if (!gameplayNetManager.Start(0)) if (!gameplayNetManager.Start(0))
{ {
throw new InvalidOperationException("The gameplay UDP socket could not start."); throw new InvalidOperationException("The gameplay UDP socket could not start.");
} }
``` ```
LiteNetLib defaults to one QoS channel. Set `ChannelsCount` before `Start` when
the game protocol uses more than one; both game processes must agree. Rendezvous
does not reserve or reinterpret any gameplay channel.
The host polls join invitations asynchronously; that method only queues a The host polls join invitations asynchronously; that method only queues a
snapshot and never calls the manager. `Poll()` is the sole SDK path that invokes snapshot and never calls the manager. `Poll()` is the sole SDK path that invokes
LiteNetLib and dispatches its synchronized callbacks. Call it once per game LiteNetLib and dispatches its synchronized callbacks. Call it once per game
@@ -53,7 +53,7 @@ public sealed partial class DocumentationContractTests
Assert.Contains("now + 600", helper, StringComparison.Ordinal); Assert.Contains("now + 600", helper, StringComparison.Ordinal);
Assert.Contains("stat.S_ISLNK", helper, StringComparison.Ordinal); Assert.Contains("stat.S_ISLNK", helper, StringComparison.Ordinal);
Assert.Contains("parent.st_mode & 0o077", helper, StringComparison.Ordinal); Assert.Contains("parent.st_mode & 0o077", helper, StringComparison.Ordinal);
Assert.Contains("metadata.st_mode & 0o022", helper, StringComparison.Ordinal); Assert.Contains("metadata.st_mode & 0o077", helper, StringComparison.Ordinal);
Assert.Contains("metadata.st_nlink != 1", helper, StringComparison.Ordinal); Assert.Contains("metadata.st_nlink != 1", helper, StringComparison.Ordinal);
Assert.Contains("mint-local-publisher-credential.sh", smoke, StringComparison.Ordinal); Assert.Contains("mint-local-publisher-credential.sh", smoke, StringComparison.Ordinal);
Assert.DoesNotContain("hexkey:", smoke, StringComparison.Ordinal); Assert.DoesNotContain("hexkey:", smoke, StringComparison.Ordinal);
@@ -184,6 +184,32 @@ public sealed partial class DocumentationContractTests
Assert.Equal(httpVersion, udpVersion); Assert.Equal(httpVersion, udpVersion);
Assert.Equal(httpVersion, ticketVersion); Assert.Equal(httpVersion, ticketVersion);
Assert.Contains($"contract version `{httpVersion}`", guide, StringComparison.Ordinal); Assert.Contains($"contract version `{httpVersion}`", guide, StringComparison.Ordinal);
Assert.Contains("gameplayNetwork.ChannelsCount = 3", guide, StringComparison.Ordinal);
Assert.Contains("defaults to one QoS channel", guide, StringComparison.Ordinal);
Assert.Contains("Rendezvous does not choose, remap, or reserve", guide, StringComparison.Ordinal);
}
[Fact]
public void SpaceGamePilotEvidenceSeparatesProvenBehaviorFromOpenGates()
{
string root = FindRepositoryRoot();
string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "spacegame-pilot.md"));
using JsonDocument evidence = JsonDocument.Parse(File.ReadAllText(
Path.Combine(root, "docs", "evidence", "consumers", "spacegame.json")));
JsonElement record = evidence.RootElement;
Assert.Equal("checkpoint-pass-with-open-gates", record.GetProperty("result").GetString());
Assert.Equal(0, record.GetProperty("localRun").GetProperty("rendezvousGameplayPayloads").GetInt32());
Assert.True(record.GetProperty("localRun").GetProperty("directGameplay").GetBoolean());
Assert.Equal(29, record.GetProperty("verification").GetProperty("debugTests").GetProperty("passed").GetInt32());
Assert.Equal(29, record.GetProperty("verification").GetProperty("releaseTests").GetProperty("passed").GetInt32());
Assert.Contains("public-package-restore", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
Assert.Contains("actual-godot-process-integration", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
Assert.Contains("dedicated-fallback-connection", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
Assert.Contains("Do not mark #21 passed", guide, StringComparison.Ordinal);
Assert.Contains("Rendezvous reserves no gameplay", guide, StringComparison.Ordinal);
Assert.Contains("not runtime proof", guide, StringComparison.Ordinal);
Assert.DoesNotMatch(ReusableCredential(), guide);
} }
[GeneratedRegex(@"rv1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+", RegexOptions.CultureInvariant)] [GeneratedRegex(@"rv1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+", RegexOptions.CultureInvariant)]