docs(integration): verify Godot and Linux SpaceGame pilot (#21)
quality-gate / quality (push) Failing after 1m40s
quality-gate / container (push) Has been skipped

This commit is contained in:
KyuubiYoru
2026-07-16 20:32:53 +02:00
parent ebb5eb617c
commit 9e863ebf64
5 changed files with 113 additions and 66 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
{ {
"AllowedHosts": "localhost;127.0.0.1", "AllowedHosts": "localhost;127.0.0.1;rendezvous",
"Rendezvous": { "Rendezvous": {
"Deployment": { "Deployment": {
"PublicHttpBaseUrl": "https://localhost/", "PublicHttpBaseUrl": "https://localhost/",
@@ -52,7 +52,7 @@
"MaxListingsPerPrincipal": 10, "MaxListingsPerPrincipal": 10,
"MaxAnonymousListingsPerAddress": 0, "MaxAnonymousListingsPerAddress": 0,
"MaxActiveJoinAttempts": 100, "MaxActiveJoinAttempts": 100,
"FallbackPolicy": "Disabled" "FallbackPolicy": "DedicatedEndpointAllowed"
} }
] ]
} }
+5 -3
View File
@@ -42,9 +42,11 @@ test "$RENDEZVOUS_UID" -ne 0
docker compose -f deploy/compose/compose.yaml up --build --detach docker compose -f deploy/compose/compose.yaml up --build --detach
``` ```
`deploy/compose/appsettings.Production.json` is an isolated loopback smoke `deploy/compose/appsettings.Production.json` is a local/private-bridge smoke
profile, not an Internet template: it deliberately opts into private advertised profile, not an Internet template: TCP is published only on host loopback, the
endpoints and has no TLS proxy. Its random key is ignored by Git and must be explicit `rendezvous` host name serves isolated clients on the Compose network,
and the profile deliberately opts into private advertised endpoints without a
TLS proxy. Its random key is ignored by Git and must be
deleted after use. Its deliberately long key window only keeps this disposable deleted after use. Its deliberately long key window only keeps this disposable
local fixture usable; production keys require short, reviewed rotation windows. local fixture usable; production keys require short, reviewed rotation windows.
Production configuration must use its real public names and must leave Production configuration must use its real public names and must leave
+45 -21
View File
@@ -3,60 +3,84 @@
"recordedAt": "2026-07-16", "recordedAt": "2026-07-16",
"issue": 21, "issue": 21,
"consumerIssue": "Kyuubi/SpaceGame#3", "consumerIssue": "Kyuubi/SpaceGame#3",
"result": "checkpoint-pass-with-open-gates", "result": "checkpoint-pass-with-external-gates",
"rendezvousCommit": "7fb85059fb7003ca0984ebd0cc9541218d33ae93", "rendezvousBaseCommit": "ebb5eb617c0bbb170418afab396b68584b7f992e",
"consumerCommit": "86424ebde69a7eeae110106ab5f84c68f04af89b", "consumerCommit": "f3f5bc29810c362656cd7143bec1ddc2cfaf9f22",
"consumerIssueComment": 11469,
"packages": { "packages": {
"FinalFactory.Rendezvous.Client": { "FinalFactory.Rendezvous.Client": {
"version": "1.0.0", "version": "1.0.0",
"source": "local-candidate", "source": "local-candidate",
"sha256": "606711e4961867ca8fe95fde973aa85f2b6e7483cafe4867ca8daa918aa78b6b" "sourceCommit": "07004cd75fe172aa5dfdb3edda22fc280a4c4477",
"sha256": "fb156cf48b49f75c244dd25ea7cc4aa9fc6fab0a878393bb7efd5d9b131d0395"
}, },
"FinalFactory.Rendezvous.Contracts": { "FinalFactory.Rendezvous.Contracts": {
"version": "1.0.0", "version": "1.0.0",
"source": "local-candidate", "source": "local-candidate",
"sha256": "394acf0a1419690829ff38eb214b1833cf64edbc85bbb7182345bfe77b8ec1f0" "sourceCommit": "07004cd75fe172aa5dfdb3edda22fc280a4c4477",
"sha256": "a82ba986d3905d599096d1d8ce8f32cd4feb104abfca37b0f65e0d2ef3df9a6f"
}, },
"LiteNetLib": { "LiteNetLib": {
"version": "2.1.4" "version": "2.1.4"
} }
}, },
"localRun": { "localRun": {
"processes": ["Rendezvous", "SpaceGame host pilot", "SpaceGame client pilot"], "processes": ["Rendezvous", "Godot SpaceGame host", "two sequential Godot SpaceGame clients"],
"typedOutcome": "Connected", "typedOutcome": "Connected",
"gameAdmission": "Accepted", "gameAdmission": "Accepted",
"directGameplay": true, "directGameplay": true,
"directInputs": 1, "authenticatedSessions": 2,
"directSnapshots": 1, "directInputs": 2,
"rendezvousGameplayPayloads": 0, "directSnapshots": 2,
"lifecyclePackets": 4,
"gameplayTransport": "caller-owned-litenetlib",
"rendezvousGameplayPayloadPath": "none",
"hostLeaseRenewed": true,
"reconnected": true,
"deregistered": true "deregistered": true
}, },
"linuxRun": {
"runtime": "Godot 4.7 .NET Linux x86_64",
"freshExport": true,
"sourceDirty": false,
"optimized": true,
"dedicatedHostNamespace": "docker",
"remoteClientNamespace": "docker",
"topology": "private-bridge",
"directGameplay": true,
"fallback": "PunchTimedOut to explicit Docker-gateway endpoint, then Accepted game admission and direct gameplay",
"artifactHashes": "SpaceGame issue #3 comment 11469"
},
"negativePaths": { "negativePaths": {
"incompatibleProtocol": "proven", "incompatibleProtocol": "proven",
"staleHostPresence": "proven", "staleHostPresence": "proven",
"punchTimeout": "proven", "punchTimeout": "proven",
"invalidAdmission": "unit-and-protocol-boundary-proven", "invalidAdmission": "integration-proven",
"capacity": "regression-tested", "capacity": "regression-tested",
"fallbackConnection": "open", "fallbackConnection": "godot-and-isolated-linux-proven",
"reconnect": "open" "reconnect": "proven"
}, },
"verification": { "verification": {
"debugBuild": "passed", "debugBuild": "passed",
"releaseBuild": "passed", "releaseBuild": "passed",
"debugTests": { "passed": 29, "failed": 0 }, "debugTests": { "passed": 31, "failed": 0 },
"releaseTests": { "passed": 29, "failed": 0 }, "releaseTests": { "passed": 31, "failed": 0 },
"exportRelease": "optimized-without-debug-symbols",
"format": "passed", "format": "passed",
"shellcheck": "passed", "shellcheck": "passed",
"twoProcessHarness": "passed", "godotReconnectHarness": "passed",
"godotFallbackHarness": "passed",
"linuxContainerHarness": "passed-clean-source",
"failureMatrix": "passed",
"adversarialReview": "passed-after-fixes" "adversarialReview": "passed-after-fixes"
}, },
"openGates": [ "openGates": [
"public-package-restore", "public-package-restore",
"actual-godot-process-integration", "representative-external-nat"
"dedicated-fallback-connection", ],
"disconnect-reconnect", "relatedSpaceGameGates": [
"remote-linux-dedicated", "production-enet-replacement",
"representative-external-nat", "capacity-profiles-64-and-128",
"spacegame-imgui-native-runtime-dependency" "sigterm-drain-save"
] ]
} }
+38 -33
View File
@@ -2,20 +2,20 @@
Tracking: Rendezvous #21 and SpaceGame #3. Tracking: Rendezvous #21 and SpaceGame #3.
The first SpaceGame checkpoint proves that the v1 client boundary can establish The current SpaceGame checkpoint proves that the v1 client boundary establishes
authenticated direct LiteNetLib traffic without taking ownership of the game's authenticated direct LiteNetLib traffic without taking ownership of the game's
protocol, admission, player identity, entity identity, capacity, or gameplay protocol, admission, player identity, entity identity, capacity, lifecycle, or
payloads. It does not yet pass the complete consumer-pilot gate. The public gameplay payloads. Real Godot processes, reconnect, an explicit dedicated
package restore, actual Godot-process integration, connected fallback transport, fallback, and a fresh Linux export now pass. The public package restore and a
remote Linux dedicated run, representative external NAT run, and reconnect path representative external NAT/CGNAT canary remain required before #21 can close.
remain required before #21 can close.
## Pinned checkpoint ## Pinned checkpoint
| Input | Value | | Input | Value |
| --- | --- | | --- | --- |
| Rendezvous source | `7fb85059fb7003ca0984ebd0cc9541218d33ae93` plus the #21 compatibility changes | | Rendezvous compatibility source | `ebb5eb617c0bbb170418afab396b68584b7f992e` plus the current #21 configuration/evidence changes |
| SpaceGame source | `86424ebde69a7eeae110106ab5f84c68f04af89b` | | Rendezvous package source | `07004cd75fe172aa5dfdb3edda22fc280a4c4477` |
| SpaceGame source | `f3f5bc29810c362656cd7143bec1ddc2cfaf9f22` |
| Client package | `FinalFactory.Rendezvous.Client` `1.0.0` | | Client package | `FinalFactory.Rendezvous.Client` `1.0.0` |
| Contracts package | `FinalFactory.Rendezvous.Contracts` `1.0.0` | | Contracts package | `FinalFactory.Rendezvous.Contracts` `1.0.0` |
| LiteNetLib | `2.1.4` | | LiteNetLib | `2.1.4` |
@@ -45,19 +45,28 @@ 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 only after admission. The player ID, entity ID, listing ID, join-attempt ID, and
LiteNetLib peer ID remain distinct values. LiteNetLib peer ID remain distinct values.
The bounded two-process harness observed: The bounded real-process harnesses observed:
- host publication and lease maintenance; - host publication and lease maintenance;
- browser compatibility filtering and join authorization; - browser compatibility filtering and join authorization;
- typed traversal outcome `Connected`; - typed traversal outcome `Connected`;
- successful audience-bound game admission; - successful audience-bound game admission;
- one reliable ordered input and one sequenced state snapshot on the direct - reliable ordered frame-definition and spawn lifecycle records, reliable
gameplay socket; ordered input, and sequenced state snapshots on the caller-owned gameplay
- zero SpaceGame gameplay payloads handled by Rendezvous; and socket;
- successful host deregistration. - disconnect and a new authenticated session for the same durable player while
LiteNetLib peers and canonical entity IDs change;
- immediate host lease renewal and successful host deregistration;
- a fresh optimized Linux export running the host and client in distinct
hardened container namespaces; and
- a forced punch timeout that connects the isolated client to an explicitly
advertised, non-loopback Docker-gateway fallback and repeats game admission.
Both Debug and Release builds passed. Both Debug and Release test runs passed 29 Rendezvous exposes no gameplay relay API; all lifecycle, command, and snapshot
tests with zero failures. The focused formatter, shell checker, clean bytes are sent by SpaceGame through its caller-owned `NetManager`. Both Debug
and Release builds passed. Both Debug and Release test runs passed 31 tests with
zero failures. ExportRelease is optimized with debug symbols removed. The
focused formatter, shell checker, fresh-export provenance gate, clean
candidate-package restore, and adversarial branch review also passed. candidate-package restore, and adversarial branch review also passed.
## Failure evidence ## Failure evidence
@@ -67,11 +76,11 @@ candidate-package restore, and adversarial branch review also passed.
| Incompatible protocol | protocol `999` returns no compatible listing and starts no traversal | Proven | | Incompatible protocol | protocol `999` returns no compatible listing and starts no traversal | Proven |
| Stale/no host presence | typed `NoHostPresence/RendezvousService/HostPresence/Mediation` | Proven | | Stale/no host presence | typed `NoHostPresence/RendezvousService/HostPresence/Mediation` | Proven |
| Traversal timeout | non-listening mediator produces typed `PunchTimedOut/LocalTraversal/NatTraversal/NatTraversal` | 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 | | Rejected game admission | invalid signature denies gameplay in the process matrix; wrong audience, expiry, and replay are regression-tested | Proven |
| Capacity and duplicate player | game-owned roster rejects both and publishes current capacity | Regression-tested | | 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 | | Configured fallback | typed `PunchTimedOut`, explicit non-loopback endpoint, same game admission, direct gameplay | Proven locally and across Linux namespaces |
| Disconnect | direct session ends and host deregisters | Proven for checkpoint shutdown | | Disconnect | host observes zero active players and final admitted count zero | Proven |
| Reconnect | a second authenticated session after disconnect | Open | | Reconnect | same durable player enters a second authenticated session with new peer/entity IDs | Proven |
## Rendezvous-side compatibility fixes ## Rendezvous-side compatibility fixes
@@ -80,6 +89,8 @@ repository:
- the local production-shaped smoke tenant accepts SpaceGame gameplay protocol - the local production-shaped smoke tenant accepts SpaceGame gameplay protocol
`2` and the bounded `mode` metadata key; `2` and the bounded `mode` metadata key;
- the Compose smoke tenant explicitly allows its private-network service name
and enables only the dedicated-endpoint fallback policy;
- SDK guidance requires games using multiple LiteNetLib QoS channels to set - SDK guidance requires games using multiple LiteNetLib QoS channels to set
`ChannelsCount` before `Start` and states that Rendezvous reserves no gameplay `ChannelsCount` before `Start` and states that Rendezvous reserves no gameplay
channel; and channel; and
@@ -90,18 +101,12 @@ Documentation contract tests cover these generic requirements.
## Remaining acceptance gates ## Remaining acceptance gates
Do not mark #21 passed until all of these have direct evidence: Do not mark #21 passed until both remaining external gates have direct evidence:
1. restore the exact immutable `1.0.0` packages from the public Gitea feed; 1. restore the exact immutable `1.0.0` packages from the public Gitea feed; and
2. drive the host and client through actual Godot game processes, not only the 2. run representative external NAT/CGNAT canaries and record the network
console pilot; topology and typed outcome.
3. implement and exercise the configured dedicated fallback connection with the
same game admission and identity rules; SpaceGame #3 remains open independently for the production ENet replacement,
4. prove disconnect followed by a fresh authenticated reconnect; 64/128-player profiles, and SIGTERM/drain/save evidence. The consumer pilot
5. run a remote client against a Linux dedicated host and capture normal does not claim those broader game-migration gates.
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.
@@ -194,21 +194,37 @@ public sealed partial class DocumentationContractTests
{ {
string root = FindRepositoryRoot(); string root = FindRepositoryRoot();
string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "spacegame-pilot.md")); string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "spacegame-pilot.md"));
string deploymentGuide = File.ReadAllText(Path.Combine(root, "docs", "deployment", "linux.md"));
using JsonDocument evidence = JsonDocument.Parse(File.ReadAllText( using JsonDocument evidence = JsonDocument.Parse(File.ReadAllText(
Path.Combine(root, "docs", "evidence", "consumers", "spacegame.json"))); Path.Combine(root, "docs", "evidence", "consumers", "spacegame.json")));
JsonElement record = evidence.RootElement; JsonElement record = evidence.RootElement;
Assert.Equal("checkpoint-pass-with-open-gates", record.GetProperty("result").GetString()); Assert.Equal("checkpoint-pass-with-external-gates", record.GetProperty("result").GetString());
Assert.Equal(0, record.GetProperty("localRun").GetProperty("rendezvousGameplayPayloads").GetInt32()); Assert.Equal("none", record.GetProperty("localRun").GetProperty("rendezvousGameplayPayloadPath").GetString());
Assert.Equal("caller-owned-litenetlib", record.GetProperty("localRun").GetProperty("gameplayTransport").GetString());
Assert.True(record.GetProperty("localRun").GetProperty("directGameplay").GetBoolean()); Assert.True(record.GetProperty("localRun").GetProperty("directGameplay").GetBoolean());
Assert.Equal(29, record.GetProperty("verification").GetProperty("debugTests").GetProperty("passed").GetInt32()); Assert.True(record.GetProperty("localRun").GetProperty("reconnected").GetBoolean());
Assert.Equal(29, record.GetProperty("verification").GetProperty("releaseTests").GetProperty("passed").GetInt32()); Assert.True(record.GetProperty("linuxRun").GetProperty("freshExport").GetBoolean());
Assert.False(record.GetProperty("linuxRun").GetProperty("sourceDirty").GetBoolean());
Assert.Equal(31, record.GetProperty("verification").GetProperty("debugTests").GetProperty("passed").GetInt32());
Assert.Equal(31, record.GetProperty("verification").GetProperty("releaseTests").GetProperty("passed").GetInt32());
Assert.Contains("public-package-restore", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString())); 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("representative-external-nat", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
Assert.Contains("dedicated-fallback-connection", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString())); Assert.DoesNotContain("actual-godot-process-integration", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
Assert.DoesNotContain("dedicated-fallback-connection", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
Assert.Contains("Do not mark #21 passed", guide, StringComparison.Ordinal); Assert.Contains("Do not mark #21 passed", guide, StringComparison.Ordinal);
Assert.Contains("Rendezvous reserves no gameplay", guide, StringComparison.Ordinal); Assert.Contains("Rendezvous reserves no gameplay", guide, StringComparison.Ordinal);
Assert.Contains("not runtime proof", guide, StringComparison.Ordinal); Assert.Contains("private-network service name", guide, StringComparison.Ordinal);
Assert.Contains("local/private-bridge smoke", deploymentGuide, StringComparison.Ordinal);
Assert.Contains("explicit `rendezvous` host name", deploymentGuide, StringComparison.Ordinal);
using JsonDocument composeSettings = JsonDocument.Parse(File.ReadAllText(
Path.Combine(root, "deploy", "compose", "appsettings.Production.json")));
JsonElement compose = composeSettings.RootElement;
Assert.Contains("rendezvous", compose.GetProperty("AllowedHosts").GetString()!.Split(';'));
JsonElement game = compose.GetProperty("Rendezvous").GetProperty("Provisioning").GetProperty("Games")[0];
Assert.Contains(2, game.GetProperty("ProtocolVersions").EnumerateArray().Select(static version => version.GetInt32()));
Assert.Equal("DedicatedEndpointAllowed", game.GetProperty("FallbackPolicy").GetString());
Assert.DoesNotMatch(ReusableCredential(), guide); Assert.DoesNotMatch(ReusableCredential(), guide);
} }