docs(integration): record SpaceGame pilot checkpoint (#21)
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user