diff --git a/docs/plans/2026-08-21-atmospheric-rendering.md b/docs/plans/2026-08-21-atmospheric-rendering.md index 9a13191e..3e6c9cfb 100644 --- a/docs/plans/2026-08-21-atmospheric-rendering.md +++ b/docs/plans/2026-08-21-atmospheric-rendering.md @@ -505,9 +505,10 @@ The exact commands, commits, metrics, artifacts, and evidence limits are in the ACE session, proving exact resource convergence and no stale maps/owners. The implemented two-client gate proved the observer login and movement, but ACE rejected the concurrent primary login when both used the available - account. The gate now accepts separate primary and observer credentials and - keeps them out of its artifacts; a distinct-account connected run must close - the authoritative nonzero `RemotePlayers` row. + account. The gate now requires dedicated observer environment credentials, + keeps them out of its artifacts, and permits independent character indexes; + a distinct-account connected run must close the authoritative nonzero + `RemotePlayers` row. 2. **EXTERNAL HARDWARE GATE.** Repeat the current clean-source RX 9070 XT pack-off/Low/Medium/High/Auto × 1080p/1440p/4K × capped/uncapped matrix on every other supported physical GPU class. The integrated-AMD Auto-to-retail diff --git a/docs/research/2026-08-22-atmospheric-campaign-completion-audit.md b/docs/research/2026-08-22-atmospheric-campaign-completion-audit.md index 8866284a..9f3daaea 100644 --- a/docs/research/2026-08-22-atmospheric-campaign-completion-audit.md +++ b/docs/research/2026-08-22-atmospheric-campaign-completion-audit.md @@ -49,9 +49,11 @@ known from this audit. Campaign AR remains deliberately **not shipped** until all three evidence classes below pass: 1. **Distinct-account remote player.** Run - `tools/run-connected-render-pack-remote-player-gate.ps1` with separate - primary and observer ACE credentials. Require a nonzero authoritative - `RemotePlayers` caster count plus a visible moving remote-player shadow. + `tools/run-connected-render-pack-remote-player-gate.ps1` with the primary + `ACDREAM_TEST_USER`/`ACDREAM_TEST_PASS` and distinct observer + `ACDREAM_TEST_OBSERVER_USER`/`ACDREAM_TEST_OBSERVER_PASS` environment + variables. Require a nonzero authoritative `RemotePlayers` caster count plus + a visible moving remote-player shadow. The same-account attempt is recorded in [`artifacts/atmospheric-rendering/connected-remote-player-20260822-140355/report.json`](../../artifacts/atmospheric-rendering/connected-remote-player-20260822-140355/report.json): the observer entered and moved, then ACE rejected the concurrent primary session. The corrected gate does not serialize credentials. 2. **Unavailable physical adapters.** On every other supported physical Vulkan diff --git a/docs/research/2026-08-22-atmospheric-stage2-connected-gate.md b/docs/research/2026-08-22-atmospheric-stage2-connected-gate.md index 9b585f4e..01d53430 100644 --- a/docs/research/2026-08-22-atmospheric-stage2-connected-gate.md +++ b/docs/research/2026-08-22-atmospheric-stage2-connected-gate.md @@ -122,11 +122,14 @@ primary client then timed out waiting for `CharacterList`: ACE did not permit a second simultaneous character from the same account. Both clients were closed cleanly, and the failed row is not represented as renderer evidence. -The gate now accepts a separate observer account and password, keeps credentials -out of its report, fails fast on session failure, and records whether distinct -accounts were configured. The authoritative nonzero `RemotePlayers` caster row -and visible moving remote-player shadow therefore require only access to a -second account; no missing renderer or test-harness implementation remains. +The gate now reads separate observer credentials from +`ACDREAM_TEST_OBSERVER_USER`/`ACDREAM_TEST_OBSERVER_PASS`, rejects same-account +configuration before launch, keeps credentials out of its report, and fails +fast on session failure. Character indexes are independently selectable because +index zero on two accounts still names distinct characters; the entered-world +identity assertion is authoritative. The nonzero `RemotePlayers` caster row and +visible moving remote-player shadow therefore require only access to a second +account; no missing renderer or test-harness implementation remains. ## Final automated gate diff --git a/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs b/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs index 3fb9e5ec..4ce98595 100644 --- a/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs +++ b/tests/AcDream.App.Tests/Diagnostics/ConnectedRenderPackGateContractTests.cs @@ -240,10 +240,10 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath { string source = ReadTool(RemotePlayerScript); Assert.Contains("PrimaryCharacterIndex = 0", source, StringComparison.Ordinal); - Assert.Contains("ObserverCharacterIndex = 1", source, StringComparison.Ordinal); - Assert.Contains("[string]$ObserverAccount", source, StringComparison.Ordinal); - Assert.Contains("[string]$ObserverPassword", source, StringComparison.Ordinal); - Assert.Contains("Primary and observer character indexes must differ", source, StringComparison.Ordinal); + Assert.Contains("ObserverCharacterIndex = 0", source, StringComparison.Ordinal); + Assert.Contains("[string]$ObserverAccount = $env:ACDREAM_TEST_OBSERVER_USER", source, StringComparison.Ordinal); + Assert.Contains("[string]$ObserverPassword = $env:ACDREAM_TEST_OBSERVER_PASS", source, StringComparison.Ordinal); + Assert.Contains("Remote-player shadow evidence requires distinct ACE accounts", source, StringComparison.Ordinal); Assert.Contains("Get-ConnectedGateBinaryIdentity", source, StringComparison.Ordinal); Assert.Contains("-CharacterIndex $ObserverCharacterIndex", source, StringComparison.Ordinal); Assert.Contains("-CharacterIndex $PrimaryCharacterIndex", source, StringComparison.Ordinal); @@ -254,6 +254,14 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath Assert.Contains("Restore-ConnectedRenderPackGateEnvironment $primaryState", source, StringComparison.Ordinal); Assert.Contains("Restore-ConnectedRenderPackGateEnvironment $observerState", source, StringComparison.Ordinal); + int reportStart = source.IndexOf("$report = [pscustomobject][ordered]@{", StringComparison.Ordinal); + Assert.True(reportStart >= 0); + string reportSource = source[reportStart..]; + Assert.DoesNotContain("$Account", reportSource, StringComparison.Ordinal); + Assert.DoesNotContain("$Password", reportSource, StringComparison.Ordinal); + Assert.DoesNotContain("$ObserverAccount", reportSource, StringComparison.Ordinal); + Assert.DoesNotContain("$ObserverPassword", reportSource, StringComparison.Ordinal); + string observer = ReadTool("connected-render-pack-remote-observer.route.txt"); AssertAppearsInOrder( observer, diff --git a/tools/run-connected-render-pack-remote-player-gate.ps1 b/tools/run-connected-render-pack-remote-player-gate.ps1 index 803833e9..d621c72c 100644 --- a/tools/run-connected-render-pack-remote-player-gate.ps1 +++ b/tools/run-connected-render-pack-remote-player-gate.ps1 @@ -3,10 +3,10 @@ param( [string]$Repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path, [string]$Account = $env:ACDREAM_TEST_USER, [string]$Password = $env:ACDREAM_TEST_PASS, - [string]$ObserverAccount, - [string]$ObserverPassword, + [string]$ObserverAccount = $env:ACDREAM_TEST_OBSERVER_USER, + [string]$ObserverPassword = $env:ACDREAM_TEST_OBSERVER_PASS, [ValidateRange(0, [int]::MaxValue)][int]$PrimaryCharacterIndex = 0, - [ValidateRange(0, [int]::MaxValue)][int]$ObserverCharacterIndex = 1, + [ValidateRange(0, [int]::MaxValue)][int]$ObserverCharacterIndex = 0, [switch]$SkipBuild, [int]$LoginTimeoutSeconds = 90 ) @@ -19,8 +19,9 @@ if ([string]::IsNullOrWhiteSpace($Account)) { $Account = 'testaccount' } if ([string]::IsNullOrWhiteSpace($Password)) { $Password = 'testpassword' } if ([string]::IsNullOrWhiteSpace($ObserverAccount)) { $ObserverAccount = $Account } if ([string]::IsNullOrWhiteSpace($ObserverPassword)) { $ObserverPassword = $Password } -if ($PrimaryCharacterIndex -eq $ObserverCharacterIndex) { - throw 'Primary and observer character indexes must differ.' +if ([string]::Equals($Account, $ObserverAccount, [StringComparison]::OrdinalIgnoreCase)) { + throw ('Remote-player shadow evidence requires distinct ACE accounts. ' + + 'Set ACDREAM_TEST_OBSERVER_USER and ACDREAM_TEST_OBSERVER_PASS.') } $startedUtc = [DateTime]::UtcNow.ToString('O') @@ -313,8 +314,7 @@ finally { TrackedSourceStatus = if ($null -eq $binaryIdentity) { @() } else { @($binaryIdentity.SourceTrackedStatus) } PrimaryCharacterIndex = $PrimaryCharacterIndex ObserverCharacterIndex = $ObserverCharacterIndex - DistinctAccountsConfigured = -not [string]::Equals( - $Account, $ObserverAccount, [StringComparison]::OrdinalIgnoreCase) + DistinctAccountsConfigured = $true Result = $result Failure = $failure }