Make Eve onboarding prove packaged authority routes
This commit is contained in:
parent
6c848bd6c0
commit
6835ea4c10
11 changed files with 151 additions and 49 deletions
|
|
@ -4,11 +4,20 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EveRoot Condition="'$(EveRoot)' == ''">$(MSBuildThisFileDirectory)..\..\..\Eve</EveRoot>
|
||||
<UsePackageArtifacts Condition="'$(UsePackageArtifacts)' == ''">false</UsePackageArtifacts>
|
||||
<CultLibPackageVersion Condition="'$(CultLibPackageVersion)' == ''">1.0.33</CultLibPackageVersion>
|
||||
<EveSurfacePackageVersion Condition="'$(EveSurfacePackageVersion)' == ''">0.3.2</EveSurfacePackageVersion>
|
||||
<RestoreAdditionalProjectSources Condition="'$(PackageFeed)' != ''">$(PackageFeed)</RestoreAdditionalProjectSources>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ItemGroup Condition="'$(UsePackageArtifacts)' != 'true'">
|
||||
<ProjectReference Include="..\..\src\GameCult.Networking.WebSockets\GameCult.Networking.WebSockets.csproj" />
|
||||
<ProjectReference Include="..\..\src\GameCult.Mesh\GameCult.Mesh.csproj" />
|
||||
<ProjectReference Include="$(EveRoot)\packages\org.gamecult.eve.surface\GameCult.Eve.Surface.csproj"
|
||||
AdditionalProperties="CultLibRoot=$(MSBuildThisFileDirectory)..\.." />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(UsePackageArtifacts)' == 'true'">
|
||||
<PackageReference Include="GameCult.Mesh" Version="$(CultLibPackageVersion)" />
|
||||
<PackageReference Include="GameCult.Networking.WebSockets" Version="$(CultLibPackageVersion)" />
|
||||
<PackageReference Include="GameCult.Eve.Surface" Version="$(EveSurfacePackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
param(
|
||||
[string] $Configuration = "Release",
|
||||
[string] $OutputDirectory = "artifacts\unity\org.gamecult.cultlib",
|
||||
[string] $NuGetConfig = "",
|
||||
[switch] $UpdateTemplate
|
||||
)
|
||||
|
||||
|
|
@ -30,11 +31,19 @@ if (Test-Path -LiteralPath $outputRoot) {
|
|||
Remove-Item -LiteralPath $outputRoot -Recurse -Force
|
||||
}
|
||||
|
||||
dotnet publish $projectPath -c $Configuration -o $publishRoot
|
||||
$publishArguments = @("publish", $projectPath, "-c", $Configuration, "-o", $publishRoot)
|
||||
if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) {
|
||||
$publishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))"
|
||||
}
|
||||
& dotnet @publishArguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "CultLib publish failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
dotnet publish $quicProjectPath -c $Configuration -o $quicPublishRoot
|
||||
$quicPublishArguments = @("publish", $quicProjectPath, "-c", $Configuration, "-o", $quicPublishRoot)
|
||||
if (-not [string]::IsNullOrWhiteSpace($NuGetConfig)) {
|
||||
$quicPublishArguments += "-p:RestoreConfigFile=$([IO.Path]::GetFullPath($NuGetConfig))"
|
||||
}
|
||||
& dotnet @quicPublishArguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "CultLib native QUIC managed publish failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
param(
|
||||
[string] $Configuration = "Release",
|
||||
[string] $OutputDirectory = "artifacts\nuget",
|
||||
[string] $PackageVersion = "",
|
||||
[switch] $SkipConsumerSmoke
|
||||
)
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ $projects = @(
|
|||
"src\GameCult.Caching.MessagePack.Analyzers\GameCult.Caching.MessagePack.Analyzers.csproj",
|
||||
"src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj",
|
||||
"src\GameCult.Networking\GameCult.Networking.csproj",
|
||||
"src\GameCult.Networking.WebSockets\GameCult.Networking.WebSockets.csproj",
|
||||
"src\GameCult.Mesh\GameCult.Mesh.csproj"
|
||||
)
|
||||
|
||||
|
|
@ -27,8 +29,14 @@ if (Test-Path -LiteralPath $outputRoot) {
|
|||
New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
|
||||
|
||||
foreach ($project in $projects) {
|
||||
dotnet pack (Join-Path $repoRoot $project) -c $Configuration -o $outputRoot `
|
||||
--nologo --verbosity quiet -p:NoWarn=1591%3BCS8632
|
||||
$packArguments = @(
|
||||
"pack", (Join-Path $repoRoot $project), "-c", $Configuration, "-o", $outputRoot,
|
||||
"--nologo", "--verbosity", "quiet", "-p:NoWarn=1591%3BCS8632"
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($PackageVersion)) {
|
||||
$packArguments += "-p:Version=$PackageVersion"
|
||||
}
|
||||
& dotnet @packArguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "NuGet pack failed for $project with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
|
@ -48,6 +56,7 @@ $expectedPackages = @(
|
|||
"GameCult.Caching.MessagePack.Analyzers.$version.nupkg",
|
||||
"GameCult.Caching.MessagePack.$version.nupkg",
|
||||
"GameCult.Networking.$version.nupkg",
|
||||
"GameCult.Networking.WebSockets.$version.nupkg",
|
||||
"GameCult.Mesh.$version.nupkg"
|
||||
)
|
||||
foreach ($package in $expectedPackages) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ import { chromium } from "playwright-core";
|
|||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const eveFlag = process.argv.indexOf("--eve-root");
|
||||
const eveRoot = resolve(eveFlag >= 0 ? process.argv[eveFlag + 1] : join(repoRoot, "..", "Eve"));
|
||||
const packageFeedFlag = process.argv.indexOf("--package-feed");
|
||||
const packageFeed = packageFeedFlag >= 0 ? resolve(process.argv[packageFeedFlag + 1]) : undefined;
|
||||
const cultLibVersionFlag = process.argv.indexOf("--cultlib-package-version");
|
||||
const cultLibPackageVersion = cultLibVersionFlag >= 0 ? process.argv[cultLibVersionFlag + 1] : undefined;
|
||||
const eveSurfaceVersionFlag = process.argv.indexOf("--eve-surface-package-version");
|
||||
const eveSurfacePackageVersion = eveSurfaceVersionFlag >= 0 ? process.argv[eveSurfaceVersionFlag + 1] : undefined;
|
||||
const nugetConfigFlag = process.argv.indexOf("--nuget-config");
|
||||
const nugetConfig = nugetConfigFlag >= 0 ? resolve(process.argv[nugetConfigFlag + 1]) : undefined;
|
||||
const sampleRoot = join(repoRoot, "samples", "eve-browser-network");
|
||||
const workRoot = await mkdtemp(join(tmpdir(), "cultmesh-browser-network-"));
|
||||
const statePath = join(workRoot, "counter.cc");
|
||||
|
|
@ -51,15 +59,30 @@ try {
|
|||
const bundle = await readFile(bundlePath, "utf8");
|
||||
assert.doesNotMatch(bundle, /(?:from\s*["']node:|require\(["']node:)/u, "browser bundle contains a Node builtin import");
|
||||
|
||||
await run("dotnet", [
|
||||
const dotnetBuildArguments = [
|
||||
"build",
|
||||
join(sampleRoot, "EveBrowserNetworkSample.csproj"),
|
||||
"-m:1",
|
||||
"--verbosity", "quiet",
|
||||
"-p:NoWarn=1591%3BCS8632",
|
||||
`-p:EveRoot=${eveRoot}`,
|
||||
`-p:CultLibRoot=${repoRoot}`,
|
||||
]);
|
||||
"-p:NuGetAudit=false",
|
||||
"-p:RestoreIgnoreFailedSources=true",
|
||||
];
|
||||
if (nugetConfig) dotnetBuildArguments.push(`-p:RestoreConfigFile=${nugetConfig}`);
|
||||
if (packageFeed) {
|
||||
if (!cultLibPackageVersion || !eveSurfacePackageVersion) {
|
||||
throw new Error("Package artifact builds require both CultLib and Eve surface package versions.");
|
||||
}
|
||||
dotnetBuildArguments.push(
|
||||
"-p:UsePackageArtifacts=true",
|
||||
`-p:PackageFeed=${packageFeed}`,
|
||||
`-p:CultLibPackageVersion=${cultLibPackageVersion}`,
|
||||
`-p:EveSurfacePackageVersion=${eveSurfacePackageVersion}`,
|
||||
);
|
||||
} else {
|
||||
dotnetBuildArguments.push(`-p:EveRoot=${eveRoot}`, `-p:CultLibRoot=${repoRoot}`);
|
||||
}
|
||||
await run("dotnet", dotnetBuildArguments);
|
||||
const providerPort = await freePort();
|
||||
const decoyProviderPort = await freePort();
|
||||
const replacementProviderPort = await freePort();
|
||||
|
|
|
|||
|
|
@ -25,43 +25,62 @@ if ($cultMeshClientSource -match 'ConnectAsync\s*\(\s*string\s+(endpointId|verse
|
|||
$cultMeshClientSource -match 'Lease(Document|Collection)Async[^\(]*\(\s*string\s+(endpointId|verseId)') {
|
||||
throw "CultMeshClient must not expose an ambiguous one-string session identity."
|
||||
}
|
||||
$discoverySource = Get-Content -LiteralPath (Join-Path $cultLibRoot "src\GameCult.Mesh\CultMeshDiscoveryService.cs") -Raw
|
||||
if ($discoverySource -notmatch 'AuthorityRuntimeIds\.Contains\(query\.AuthorityRuntimeId') {
|
||||
throw "CultMesh discovery must prove that a selected Verse route advertises the requested authority runtime."
|
||||
}
|
||||
|
||||
$dotnetPackageRoot = Join-Path ([IO.Path]::GetTempPath()) `
|
||||
("eve-getting-started-nuget-" + [guid]::NewGuid().ToString("N"))
|
||||
$sourceRevision = (& git -C $cultLibRoot rev-parse --short=12 HEAD).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($sourceRevision)) {
|
||||
throw "Could not determine the CultLib source revision for local package identity."
|
||||
}
|
||||
$verificationNonce = [guid]::NewGuid().ToString("N").Substring(0, 8)
|
||||
$cultLibLocalVersion = "1.0.33-local.$sourceRevision.$verificationNonce"
|
||||
$surfaceLocalVersion = "0.3.2-local.$sourceRevision.$verificationNonce"
|
||||
try {
|
||||
& (Join-Path $EveRoot "scripts\pack-dotnet-surface.ps1") `
|
||||
-CultLibRoot $cultLibRoot `
|
||||
-OutputDirectory $dotnetPackageRoot
|
||||
-OutputDirectory $dotnetPackageRoot `
|
||||
-CultLibPackageVersion $cultLibLocalVersion `
|
||||
-SurfacePackageVersion $surfaceLocalVersion
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "The clean .NET package-consumer checkpoint failed."
|
||||
}
|
||||
|
||||
$cultLibPackage = Get-ChildItem -LiteralPath $dotnetPackageRoot -Filter "GameCult.Mesh.*.nupkg" |
|
||||
Where-Object { $_.Name -notlike "*.snupkg" } |
|
||||
Select-Object -First 1
|
||||
$surfacePackage = Get-ChildItem -LiteralPath $dotnetPackageRoot -Filter "GameCult.Eve.Surface.*.nupkg" |
|
||||
Where-Object { $_.Name -notlike "*.snupkg" } |
|
||||
Select-Object -First 1
|
||||
if (-not $cultLibPackage -or -not $surfacePackage) {
|
||||
throw "The local package feed is missing CultMesh or Eve surface artifacts."
|
||||
}
|
||||
$cultLibVersion = [regex]::Match($cultLibPackage.Name, '^GameCult\.Mesh\.(.+)\.nupkg$').Groups[1].Value
|
||||
$surfaceVersion = [regex]::Match($surfacePackage.Name, '^GameCult\.Eve\.Surface\.(.+)\.nupkg$').Groups[1].Value
|
||||
|
||||
& (Join-Path $PSScriptRoot "verify-eve-two-runtime-sample.ps1") `
|
||||
-EveRoot $EveRoot `
|
||||
-SkipDependencyInstall:$SkipDependencyInstall
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "The clean package-consumer checkpoint failed."
|
||||
}
|
||||
|
||||
$nodeCommand = Get-Command node -ErrorAction Stop
|
||||
$playwrightCli = Join-Path $cultLibRoot "node_modules\playwright-core\cli.js"
|
||||
if (-not (Test-Path -LiteralPath $playwrightCli -PathType Leaf)) {
|
||||
throw "playwright-core is missing after dependency setup: $playwrightCli"
|
||||
}
|
||||
|
||||
& $nodeCommand.Source (Join-Path $PSScriptRoot "verify-eve-browser-network.mjs") `
|
||||
--eve-root $EveRoot `
|
||||
--package-feed $dotnetPackageRoot `
|
||||
--cultlib-package-version $cultLibVersion `
|
||||
--eve-surface-package-version $surfaceVersion
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "The real browser/C# network checkpoint failed."
|
||||
}
|
||||
|
||||
Write-Host "Eve getting-started verification passed: clean .NET and TypeScript artifacts, DOM and headless convergence, real Chromium and C# clients, Odin discovery, authority-bound routes, receipts, persistence, and route replacement."
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $dotnetPackageRoot) {
|
||||
Remove-Item -LiteralPath $dotnetPackageRoot -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
& (Join-Path $PSScriptRoot "verify-eve-two-runtime-sample.ps1") `
|
||||
-EveRoot $EveRoot `
|
||||
-SkipDependencyInstall:$SkipDependencyInstall
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "The clean package-consumer checkpoint failed."
|
||||
}
|
||||
|
||||
$nodeCommand = Get-Command node -ErrorAction Stop
|
||||
$playwrightCli = Join-Path $cultLibRoot "node_modules\playwright-core\cli.js"
|
||||
if (-not (Test-Path -LiteralPath $playwrightCli -PathType Leaf)) {
|
||||
throw "playwright-core is missing after dependency setup: $playwrightCli"
|
||||
}
|
||||
|
||||
& $nodeCommand.Source (Join-Path $PSScriptRoot "verify-eve-browser-network.mjs") `
|
||||
--eve-root $EveRoot
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "The real browser/C# network checkpoint failed."
|
||||
}
|
||||
|
||||
Write-Host "Eve getting-started verification passed: clean .NET and TypeScript artifacts, DOM and headless convergence, real Chromium and C# clients, Odin discovery, receipts, persistence, and route replacement."
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ using var node = await CultMesh.CreateNodeAsync("counter.cc", new CultMeshNodeOp
|
|||
CacheOptions = new CultCacheOpenOptions { Registry = registry },
|
||||
DatabaseOptions = new CultNetDatabaseOptions
|
||||
{
|
||||
RuntimeId = "sample.counter-provider",
|
||||
RuntimeId = "sample.counter-daemon",
|
||||
DocumentRegistry = documents
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
# 2. Connect A Client By Stable Identity
|
||||
|
||||
Clients address a live session by stable Verse/provider identity. In this tutorial,
|
||||
`sample.counter` is the Verse id and `sample.counter-provider` is the authority
|
||||
`sample.counter` is the Verse id and `sample.counter-daemon` is the authority
|
||||
runtime Odin currently advertises for it. Application clients pass that typed
|
||||
pair, never a socket address and never one ambiguous string that might mean
|
||||
either identity. A product may expose only a Verse choice to its user; its
|
||||
selection owner then resolves the provider runtime from Odin's descriptor before
|
||||
opening the session. Discovery owns the current physical routes, and the session
|
||||
manager owns connection reuse, path rotation, and transport failure state.
|
||||
Each Odin route binds that authority runtime to one endpoint, protocol set, and
|
||||
generation. The client verifies the same tuple with the connected peer before
|
||||
the session becomes online; runtime membership and endpoint reachability are
|
||||
not independent routing evidence.
|
||||
|
||||
```csharp
|
||||
using GameCult.Mesh;
|
||||
|
|
@ -15,7 +19,9 @@ using GameCult.Mesh;
|
|||
var odinEndpoint = Environment.GetEnvironmentVariable("ODIN_ENDPOINT")
|
||||
?? throw new InvalidOperationException("Set ODIN_ENDPOINT to the configured rendezvous route.");
|
||||
const string verseId = "sample.counter";
|
||||
var target = new CultMeshSessionTarget(verseId, "sample.counter-provider");
|
||||
var target = new CultMeshSessionTarget(verseId, "sample.counter-daemon");
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
||||
var cancellationToken = timeout.Token;
|
||||
using var mesh = new CultMeshClient(new CultMeshClientOptions
|
||||
{
|
||||
RendezvousEndpoints = new[] { odinEndpoint }
|
||||
|
|
@ -65,6 +71,11 @@ network proof):
|
|||
dotnet test tests/GameCult.Mesh.Tests/GameCult.Mesh.Tests.csproj --filter FullyQualifiedName~CultMeshSessionManagerTests
|
||||
```
|
||||
|
||||
The real checkpoint in chapter 4 additionally advertises a better-priority
|
||||
endpoint for the wrong authority runtime. Both the C# and browser clients must
|
||||
ignore it, verify the intended peer, survive route replacement, and reject
|
||||
wrong-source operation responses.
|
||||
|
||||
Advanced hosts may inject lookup sources, connectors, clocks, persistence, and
|
||||
diagnostics through the lower-level discovery and session APIs. Application
|
||||
features should not need them.
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ GameCult dependency is `PackageReference Include="GameCult.Eve.Surface"`.
|
|||
This proves the renderer-neutral provider contract does not require a sibling
|
||||
Eve or CultLib source tree after packaging.
|
||||
|
||||
The real-process C# sample is then rebuilt from that same temporary NuGet feed.
|
||||
The source project references in its `.csproj` are contributor convenience only
|
||||
and are disabled by the checkpoint; they are not the consumer contract.
|
||||
|
||||
The first layer builds and packs `cultcache-ts`, `cultnet-ts`, `cultmesh-ts`,
|
||||
`@gamecult/eve-contracts`, and `@gamecult/eve-browser-lowering`, installs the
|
||||
tarballs into an empty temporary consumer, and runs
|
||||
|
|
@ -108,11 +112,11 @@ both consumers converge on the same state and canonical receipt identity, a
|
|||
duplicate idempotency key is not applied twice, and the state plus receipt
|
||||
survive reopening the `.cc` store.
|
||||
|
||||
The second layer runs a durable C# provider, a separate local Odin fixture, a
|
||||
real Chromium Eve lowerer, and an independent C# headless observer over binary
|
||||
The second layer runs a durable C# provider, a wrong-authority decoy, a separate
|
||||
local Odin fixture, a real Chromium Eve lowerer, and an independent C# headless observer over binary
|
||||
CultNet WebSocket lanes. The open browser resolves the `sample.counter` Verse
|
||||
through the canonical catalog, survives its provider restarting on a different route,
|
||||
and resubscribes. The retained C# client then invokes a second typed operation
|
||||
through the canonical catalog, rejects the decoy route, survives its provider
|
||||
restarting on a different route, and resubscribes. The retained C# client then invokes a second typed operation
|
||||
through `CultMeshClient.InvokeAsync`; Chromium and C# observe the same
|
||||
provider-authored state and receipt chronology. The same retained C# session
|
||||
then completes 10,000 typed no-op operations under explicit p99 and post-GC
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ The counter is now one small multiplayer game:
|
|||
typed operation boundary;
|
||||
- both clients remain attached when Odin moves the Verse to a replacement
|
||||
physical provider route.
|
||||
- a better-priority route bound to another runtime remains unreachable to both
|
||||
clients throughout the chronology.
|
||||
|
||||
Run the whole chronology from the CultLib checkout:
|
||||
|
||||
|
|
@ -34,8 +36,10 @@ packages, installs those artifacts into an empty temporary consumer, and proves
|
|||
DOM/headless convergence plus `.cc` reopen. This rejects a sample that only
|
||||
works through repository-relative source imports.
|
||||
|
||||
The third checkpoint boots separate Odin, provider, Chromium, and C# headless
|
||||
processes. Chromium performs the first increment. The provider then restarts on
|
||||
The third checkpoint boots separate Odin, intended provider, wrong-authority
|
||||
decoy provider, Chromium, and C# headless processes. Its C# application is built
|
||||
from the temporary NuGet feed produced by the first checkpoint, not sibling
|
||||
project references. Chromium performs the first increment. The provider then restarts on
|
||||
a different route, Odin advertises the replacement, and the retained clients
|
||||
resubscribe without application reconnect code. C# performs the second
|
||||
increment. Both clients must observe count 2 and both canonical receipt ids.
|
||||
|
|
|
|||
|
|
@ -4,14 +4,22 @@ This series builds one typed application state and presents it through multiple
|
|||
Eve runtimes. It is maintained as an API acceptance test: examples use public
|
||||
APIs, name the owner of each decision, and include a verification checkpoint.
|
||||
|
||||
Run the complete getting-started checkpoint:
|
||||
Application consumers install released packages and do not need the source
|
||||
repositories. This checkout also contains a maintainer checkpoint that creates
|
||||
the same clean consumers from local package artifacts:
|
||||
|
||||
```powershell
|
||||
pwsh -File ./scripts/verify-eve-getting-started.ps1
|
||||
```
|
||||
|
||||
With clean sibling CultLib and Eve checkouts, the command installs missing build
|
||||
dependencies and runs three explicit evidence layers. First it packs the
|
||||
The maintainer command takes an explicit `-EveRoot` when Eve is not a sibling:
|
||||
|
||||
```powershell
|
||||
pwsh -File ./scripts/verify-eve-getting-started.ps1 -EveRoot D:\src\Eve
|
||||
```
|
||||
|
||||
It installs missing build dependencies and runs three explicit evidence layers.
|
||||
First it packs the
|
||||
CultLib managed dependency closure plus `GameCult.Eve.Surface`, then restores
|
||||
and runs an empty .NET consumer using only `PackageReference`. Next it packs the five
|
||||
TypeScript packages into another empty temporary consumer, validates the Eve surface
|
||||
|
|
@ -20,7 +28,8 @@ headless observer converge, and reopens the durable `.cc` store. Then it boots a
|
|||
durable C# provider, local Odin fixture, real Chromium lowerer, and independent
|
||||
C# client. Chromium invokes before provider replacement; C# invokes afterward.
|
||||
Both retained clients prove discovery by identity, canonical receipts, provider
|
||||
restart on another route, resubscription, and durable convergence. The retained
|
||||
restart on another route, rejection of a better-priority wrong-authority route,
|
||||
resubscription, and durable convergence. The retained
|
||||
C# session also completes 10,000 non-persistent typed operations under p99 and
|
||||
post-GC managed-memory gates.
|
||||
Set `CHROME_PATH` only when no installed Chrome, Chromium, Edge, or Playwright
|
||||
|
|
@ -58,3 +67,6 @@ native projection. CultMesh owns discovery and connection continuity.
|
|||
pull request.
|
||||
- A setup step that depends on repository internals is an API defect, not
|
||||
acceptable tutorial folklore.
|
||||
- The maintainer command may read owner checkouts to create packages. Every
|
||||
sample process it launches must consume those package artifacts rather than
|
||||
compiling through sibling project references.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Description>CultNet schema-v0 WebSocket host adapter for browser and web runtimes</Description>
|
||||
<PackageId>GameCult.Networking.WebSockets</PackageId>
|
||||
<PackageTags>game;networking;websocket;cultnet;cultmesh</PackageTags>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GameCult.Networking\GameCult.Networking.csproj" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue