fix(client): restore retail interaction parity
All checks were successful
CI / linux-portable (push) Successful in 3m27s
CI / windows-gate (push) Successful in 6m42s
CI / release (push) Successful in 2m12s

Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
This commit is contained in:
Erik 2026-08-26 20:45:11 +02:00
parent 0c699240e0
commit f6fe0f2a4f
151 changed files with 10162 additions and 1211 deletions

View file

@ -736,6 +736,16 @@ internal sealed class LocalPlayerTeleportController
/// </summary>
private readonly ILocalPlayerLogoutOperations _logout;
/// <summary>
/// The confirmed-logoff pump must let the old streaming window finish
/// its asynchronous session retirement before Runtime exposes the next
/// character generation. The reset transaction immediately calls back
/// into <see cref="ResetGenerationPresentation"/>; this latch transfers
/// that already-completed retirement across the synchronous callback so
/// it is consumed once instead of starting a second old-window pass.
/// </summary>
private bool _logoutStreamingRetirementPrepared;
public LocalPlayerTeleportController(
ILocalPlayerTeleportAuthority authority,
ILocalPlayerTeleportInputLifetime input,
@ -984,6 +994,8 @@ internal sealed class LocalPlayerTeleportController
return false;
}
_logoutStreamingRetirementPrepared = false;
if (!_transit.TryBeginLogoutRequest(_logout.IsLocalPlayerKiller))
return false;
@ -1088,8 +1100,23 @@ internal sealed class LocalPlayerTeleportController
private void CompleteLogoutHandoff(long generation)
{
if (!_transit.CompleteLogout() || _lifetimeGeneration != generation)
// Reset(sessionEnding: true) is a retained, frame-budgeted old-world
// retirement. Do not let CompleteCharacterLogOff expose the fresh
// Runtime generation until that barrier has converged; otherwise a
// quick re-entry can inherit the origin-recenter gate and remain in
// portal space with no landblocks admitted (lb 0/0).
if (!_streaming.ResetRecenter(sessionEnding: true)
|| _lifetimeGeneration != generation)
{
return;
}
_logoutStreamingRetirementPrepared = true;
if (!_transit.CompleteLogout() || _lifetimeGeneration != generation)
{
_logoutStreamingRetirementPrepared = false;
return;
}
Console.WriteLine(
"live: logout confirmed — returning to character select");
@ -1102,6 +1129,8 @@ internal sealed class LocalPlayerTeleportController
return;
}
_logoutStreamingRetirementPrepared = false;
// The transaction refused or degraded to a full stop. If a reset
// reached this controller the lifetime moved and everything is
// already clean; otherwise retire the presentation here so a
@ -1927,6 +1956,11 @@ internal sealed class LocalPlayerTeleportController
bool clearSession,
bool resetCanonicalTransit = false)
{
bool streamingRetirementPrepared = clearSession
&& _logoutStreamingRetirementPrepared;
if (clearSession)
_logoutStreamingRetirementPrepared = false;
long generation = checked(++_lifetimeGeneration);
_pendingCell = 0u;
@ -1951,7 +1985,8 @@ internal sealed class LocalPlayerTeleportController
if (clearSession)
_loginPlacementCompleted = false;
_streaming.ResetRecenter(clearSession);
if (!streamingRetirementPrepared)
_streaming.ResetRecenter(clearSession);
if (_lifetimeGeneration != generation)
return generation;