diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index 42d3237a..9c7b3005 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -9,6 +9,42 @@ on: workflow_dispatch: jobs: + getting-started: + name: ${{ matrix.os }} one-command multiplayer onboarding + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest] + + steps: + - name: Check out CultLib + uses: actions/checkout@v4 + with: + path: CultLib + + - name: Check out the matching Eve contract + uses: actions/checkout@v4 + with: + repository: GameCult/Eve + ref: ${{ vars.EVE_INTEGRATION_REF || 'codex/eve-browser-host-isolation' }} + path: Eve + + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Run the documented one-command checkpoint + shell: pwsh + run: ./CultLib/scripts/verify-eve-getting-started.ps1 -EveRoot ./Eve + reactive-and-resource-lifetime: name: ${{ matrix.os }} reactive semantics and leases runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 6df5d940..68ee88d6 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,14 @@ series. Its first executable package-consumer checkpoint is [`samples/eve-two-runtime`](samples/eve-two-runtime/README.md). The real-process browser continuation is [`samples/eve-browser-network`](samples/eve-browser-network/README.md). -The implemented browser client boundary and remaining real-network gate are -mapped explicitly in +One documented command runs both layers: + +```powershell +pwsh -File ./scripts/verify-eve-getting-started.ps1 +``` + +The implemented browser client boundary and remaining deployed-infrastructure +gates are mapped explicitly in [`browser-verse-transport.md`](src/GameCult.Mesh/docs/browser-verse-transport.md). If you want only the smallest durable "open node -> put typed doc -> get typed doc" diff --git a/samples/eve-browser-network/README.md b/samples/eve-browser-network/README.md index 3b85dbbc..a78197a7 100644 --- a/samples/eve-browser-network/README.md +++ b/samples/eve-browser-network/README.md @@ -15,9 +15,15 @@ This is the real-process continuation of `samples/eve-two-runtime`: Run the proof from the CultLib repository: ```powershell -node scripts/verify-eve-browser-network.mjs --eve-root ..\Eve +pwsh -File ./scripts/verify-eve-getting-started.ps1 ``` +The wrapper first proves clean package consumption, then runs this real-process +checkpoint. Run `node scripts/verify-eve-browser-network.mjs --eve-root ../Eve` +directly only to diagnose the network layer. Browser discovery is portable: +`CHROME_PATH` wins when configured, then common Chrome/Chromium/Edge locations +and Playwright's installed Chromium are checked. + The verifier clicks the Eve button twice with one idempotency key, requires one state transition, restarts the provider on a different physical port, updates the local Odin fixture, and requires the still-open browser to rediscover the diff --git a/samples/eve-two-runtime/README.md b/samples/eve-two-runtime/README.md index 6f7fe349..c149e14e 100644 --- a/samples/eve-two-runtime/README.md +++ b/samples/eve-two-runtime/README.md @@ -12,9 +12,13 @@ Eve. It intentionally has one owner for each decision: Run it from the CultLib repository: ```powershell -pwsh -File ./scripts/verify-eve-two-runtime-sample.ps1 -EveRoot ../Eve +pwsh -File ./scripts/verify-eve-getting-started.ps1 ``` +That command continues into the real Chromium/C# network checkpoint after this +artifact layer passes. Run `verify-eve-two-runtime-sample.ps1` directly only to +diagnose clean package consumption in isolation. + From clean CultLib and Eve checkouts, the verifier installs missing build dependencies, builds package artifacts, installs them into a new empty temporary consumer, and runs `sample.mjs`. Success proves: diff --git a/scripts/verify-eve-browser-network.mjs b/scripts/verify-eve-browser-network.mjs index 9e047930..401a8dde 100644 --- a/scripts/verify-eve-browser-network.mjs +++ b/scripts/verify-eve-browser-network.mjs @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import { spawn } from "node:child_process"; +import { existsSync } from "node:fs"; import { createServer as createHttpServer } from "node:http"; import { mkdtemp, readFile, rm } from "node:fs/promises"; import { createServer as createNetServer } from "node:net"; @@ -45,7 +46,12 @@ 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", ["build", join(sampleRoot, "EveBrowserNetworkSample.csproj"), "--verbosity", "quiet"]); + await run("dotnet", [ + "build", + join(sampleRoot, "EveBrowserNetworkSample.csproj"), + "--verbosity", "quiet", + "-p:NoWarn=1591%3BCS8632", + ]); const providerPort = await freePort(); const replacementProviderPort = await freePort(); const odinPort = await freePort(); @@ -58,8 +64,7 @@ try { await headless.waitFor("HEADLESS_READY "); httpServer = await serve(httpPort, sampleRoot, bundlePath); - const executablePath = process.env.CHROME_PATH || "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"; - browser = await chromium.launch({ executablePath, headless: true }); + browser = await chromium.launch({ executablePath: resolveChromiumExecutable(), headless: true }); let page = await browser.newPage(); const url = `http://127.0.0.1:${httpPort}/?odin=${encodeURIComponent(odinEndpoint)}&token=${token}`; await page.goto(url); @@ -221,3 +226,36 @@ async function stop(process) { ]); if (process.exitCode === null) process.kill("SIGKILL"); } + +function resolveChromiumExecutable() { + const candidates = [process.env.CHROME_PATH]; + if (process.platform === "win32") { + for (const root of [process.env.ProgramFiles, process.env["ProgramFiles(x86)"], process.env.LOCALAPPDATA]) { + if (!root) continue; + candidates.push( + join(root, "Google", "Chrome", "Application", "chrome.exe"), + join(root, "Microsoft", "Edge", "Application", "msedge.exe"), + ); + } + } else if (process.platform === "darwin") { + candidates.push( + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + ); + } else { + candidates.push( + "/usr/bin/google-chrome", + "/usr/bin/google-chrome-stable", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + "/opt/google/chrome/chrome", + ); + } + candidates.push(chromium.executablePath()); + const executablePath = candidates.find(candidate => candidate && existsSync(candidate)); + if (executablePath) return executablePath; + throw new Error( + "No Chromium-family browser was found. Set CHROME_PATH or run " + + "'node node_modules/playwright-core/cli.js install chromium'.", + ); +} diff --git a/scripts/verify-eve-getting-started.ps1 b/scripts/verify-eve-getting-started.ps1 new file mode 100644 index 00000000..57dd0a61 --- /dev/null +++ b/scripts/verify-eve-getting-started.ps1 @@ -0,0 +1,29 @@ +param( + [string] $EveRoot = (Join-Path (Split-Path -Parent $PSScriptRoot) "..\Eve"), + [switch] $SkipDependencyInstall +) + +$ErrorActionPreference = "Stop" +$cultLibRoot = Split-Path -Parent $PSScriptRoot +$EveRoot = [IO.Path]::GetFullPath($EveRoot) + +& (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 artifacts, DOM and headless convergence, real Chromium and C# clients, Odin discovery, receipts, persistence, and route replacement." diff --git a/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md b/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md index 1a94cce8..91f742ac 100644 --- a/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md +++ b/src/GameCult.Mesh/docs/getting-started/03-publish-an-eve-surface.md @@ -35,14 +35,18 @@ Run the same provider and connect at least two lowerers. Both must report the same provider id, surface id, surface version, and command receipt ids. Native pixels may differ; provider truth may not. -The package-artifact DOM-lowerer/headless checkpoint is executable from CultLib: +The complete checkpoint is executable from CultLib: ```powershell cd CultLib -pwsh -File ./scripts/verify-eve-two-runtime-sample.ps1 -EveRoot ../Eve +pwsh -File ./scripts/verify-eve-getting-started.ps1 ``` -The verifier builds and packs `cultcache-ts`, `cultnet-ts`, `cultmesh-ts`, +This one command runs the clean package-artifact checkpoint and then the real +browser/C# network checkpoint. They remain separate programs because they prove +different boundaries; the developer should not have to orchestrate them. + +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 `samples/eve-two-runtime/sample.mjs`. Eve's generated runtime validators reject @@ -53,6 +57,12 @@ 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 +CultNet WebSocket lanes. The open browser resolves provider identity through +the canonical Verse catalog, survives a provider restart on a different route, +resubscribes, and commits another canonical receipt. + Current verification for the Unity consumer remains: ```powershell @@ -60,19 +70,14 @@ cd ../EveUnity pwsh -File ./scripts/run-release-consumer-tests.ps1 ``` -This first checkpoint proves clean package consumption, canonical Eve contract +The artifact layer proves clean package consumption, canonical Eve contract validation, and two independent lowering/observation consumers inside Node. It -does **not** claim a real browser process, an Odin-discovered network hop, or -cross-language command execution. Do not cite it as evidence for those layers. +does **not** prove the network layer; the second program owns that evidence. -The next executable checkpoint is +The network program is [`samples/eve-browser-network`](../../../../samples/eve-browser-network/README.md). -It runs a durable C# provider, a separate local Odin fixture, a real Chromium -Eve lowerer, and an independent C# headless observer over authenticated binary -CultNet WebSocket lanes. The still-open browser resolves stable identity through -the canonical Verse catalog, survives a provider restart on a different -physical route, resubscribes, and commits another canonical receipt. A smoke -against the deployed Odin daemon and retained C# lease reconnection remain -separate infrastructure gates. Their owner map and full acceptance contract are recorded in +Run it separately only while diagnosing that layer. A smoke against the +deployed Odin daemon and retained C# lease reconnection remain separate +infrastructure gates. Their owner map and full acceptance contract are recorded in [`browser-verse-transport.md`](../browser-verse-transport.md). diff --git a/src/GameCult.Mesh/docs/getting-started/README.md b/src/GameCult.Mesh/docs/getting-started/README.md index ebc38057..44bd5e2c 100644 --- a/src/GameCult.Mesh/docs/getting-started/README.md +++ b/src/GameCult.Mesh/docs/getting-started/README.md @@ -4,18 +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 current clean-consumer checkpoint first: +Run the complete getting-started checkpoint: ```powershell -pwsh -File ./scripts/verify-eve-two-runtime-sample.ps1 -EveRoot ../Eve +pwsh -File ./scripts/verify-eve-getting-started.ps1 ``` -From clean CultLib and Eve checkouts, the command installs the package build -dependencies when absent, packs the five TypeScript packages, installs only -those artifacts into an empty temporary project, validates the Eve -surface and receipt, executes one idempotent counter command, proves two local -consumers converge, and reopens the durable `.cc` store. It is deliberately not -called a networked-browser proof; chapter 3 names that remaining boundary. +With clean sibling CultLib and Eve checkouts, the command installs missing build +dependencies and runs two explicit evidence layers. First it packs the five +TypeScript packages into an empty temporary consumer, validates the Eve surface +and receipt, executes one idempotent counter command, proves a DOM lowerer and +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# observer; it proves discovery by identity, commands, canonical receipts, +provider restart on another route, resubscription, and durable convergence. +Set `CHROME_PATH` only when no installed Chrome, Chromium, Edge, or Playwright +Chromium can be found automatically. Follow the chapters in order: