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

@ -1,5 +1,6 @@
using AcDream.Core.Plugins;
using AcDream.App.Composition;
using AcDream.App.Input;
using AcDream.App.Physics;
using AcDream.App.Rendering.Gpu;
using AcDream.App.Rendering.Scene;
@ -17,6 +18,7 @@ using DatReaderWriter;
using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.Windowing;
using AcDream.UI.Abstractions.Input;
namespace AcDream.App.Rendering;
@ -599,14 +601,18 @@ public sealed class GameWindow :
// startup — no other call to RetailDefaults() / AcdreamCurrentDefaults()
// should land in the GameWindow construction path.
private readonly AcDream.UI.Abstractions.Input.KeyBindings _keyBindings;
private bool _keyBindingsPersisted;
private readonly GraphicalHostPlatformServices _platformServices;
private readonly ApplicationPathSet _applicationPaths;
private static AcDream.UI.Abstractions.Input.KeyBindings LoadStartupKeyBindings(
string path)
{
var bindings = AcDream.UI.Abstractions.Input.KeyBindings.LoadOrDefault(path);
Console.WriteLine($"keybinds: loaded {bindings.All.Count} bindings from {path}");
var bindings = AcDream.App.Input.RetailKeymapProfileStore.LoadActiveOrJson(
path, out string profileName);
Console.WriteLine(
$"keybinds: loaded {bindings.All.Count} bindings; active retail profile "
+ $"'{profileName}', JSON mirror {path}");
return bindings;
}
@ -1522,7 +1528,8 @@ public sealed class GameWindow :
hostInputCamera.GpuFrameLifetime,
() => WorldTime.CurrentCalendar,
settingsDevTools.RenderPacks,
_renderPackDiagnostics.CaptureDiagnostics),
_renderPackDiagnostics.CaptureDiagnostics,
_applicationPaths.ScreenshotsDirectory),
_retailUiLease,
this).Compose(
platformResult,
@ -1569,6 +1576,7 @@ public sealed class GameWindow :
_cellVisibility,
_liveWorldOrigin,
_localPlayerIdentity,
_chaseCameraInput,
_pointerPosition,
_playerApproachCompletions,
_renderResourceLifetime,
@ -1821,6 +1829,7 @@ public sealed class GameWindow :
if (!_lifetime.HasShutdownRoots)
{
PersistKeyBindingsAtShutdown();
// Campaign LA slice LA1: capture BEFORE the shutdown roots run —
// by the time teardown completes, IsInWorld is always false
// regardless of whether a real session was ever connected.
@ -1861,6 +1870,33 @@ public sealed class GameWindow :
ReportExited(report);
}
private void PersistKeyBindingsAtShutdown()
{
// Construction-only tests and failed starts never create the input
// dispatcher. They must not materialize a profile in the real user's
// Documents folder merely because the half-built window is disposed.
if (_keyBindingsPersisted || _inputDispatcher is null) return;
_keyBindingsPersisted = true;
try
{
KeyBindings current = _inputDispatcher.Bindings;
var profiles = new AcDream.App.Input.RetailKeymapProfileStore(
_applicationPaths.KeyBindingsFile);
RetailKeymapSaveResult saved = profiles.SaveActive(current);
if (saved.Status != RetailKeymapSaveStatus.Saved)
{
Console.WriteLine(
$"keymap: shutdown save failed ({saved.Status}): {saved.Error}");
return;
}
current.SaveToFile(_applicationPaths.KeyBindingsFile);
}
catch (Exception failure)
{
Console.WriteLine($"keymap: shutdown persistence failed: {failure.Message}");
}
}
/// <summary>
/// Writes the ONE terminal "exited" status event for this session
/// (fix #406). A resource-shutdown transaction can converge cleanly