docs(integration): verify Godot and Linux SpaceGame pilot (#21)
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user