fix(#192): close login streaming race — gate on a known real position, not session state
User-reported: logging in at a non-Holtburg position sometimes showed stabs/scenery floating in the wrong place. Root-caused via a threading/ lifecycle trace, not inference: WorldSession transitions to InWorld immediately after the login handshake (WorldSession.cs:608) — well before the player's own spawn CreateObject (which carries their real position) has arrived over the network. The streaming gate's old condition (`!IsLiveModeWaitingForLogin || liveInWorld`) opened the instant InWorld fired, letting the background streaming worker (LandblockStreamer's dedicated Thread) build real landblocks using whatever _liveCenterX/Y held at that moment — the Holtburg startup placeholder, not a "not known yet" sentinel. Landblocks that started building in that window bake their world offset from that placeholder at build time; if their build was still in flight when the real spawn arrived and recentered the world (ForceReloadWindow, which only unloads already-RESIDENT landblocks), they finished and got applied anyway — stale-positioned geometry landing wherever the guess put it relative to whatever streamed in afterward with the corrected center. Confirmed with the user this wasn't "wrong placeholder, need a better one" — any placeholder racing against the real answer reproduces the same bug. The fix removes the race instead: StreamingReadinessGate .ShouldStream requires an explicit liveCenterKnown flag (true only once the player's own spawn has been processed) in addition to liveInWorld. Nothing streams in live mode until the real position is confirmed — no placeholder value is ever acted on. Preserves the #106 gate-3 fix this gate originally existed for (auto-entry waits for terrain under the spawn; terrain streaming must not wait for chase mode in turn, or the two deadlock) since liveCenterKnown becomes true independently of chase mode, driven purely by the spawn packet's arrival. The stricter render gate (GameWindow.cs:9596, hides ALL world geometry until chase mode engages, no relaxation) already provided a partial safety net and is unchanged — this fix stops the stale geometry from ever being built, rather than relying on the render gate to hide it until the reveal. Core 2680+2skip / App 741+2skip / UI 425 / Net 385 green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3dcd53cab7
commit
fa9aedca0a
3 changed files with 149 additions and 1 deletions
|
|
@ -969,6 +969,15 @@ public sealed class GameWindow : IDisposable
|
|||
private AcDream.Core.Net.WorldSession? _liveSession;
|
||||
private int _liveCenterX;
|
||||
private int _liveCenterY;
|
||||
// #192 (2026-07-09): true once the player's OWN spawn CreateObject has been
|
||||
// received and processed — i.e. _liveCenterX/Y reflects a real,
|
||||
// server-confirmed position, not the Holtburg startup placeholder. Gates
|
||||
// streaming via StreamingReadinessGate.ShouldStream so no landblock is ever
|
||||
// built around a still-guessed center in live mode. See
|
||||
// AcDream.Core.World.StreamingReadinessGate for the full mechanism this
|
||||
// closes (stale-centered geometry built during the InWorld-but-not-yet-
|
||||
// located window, applied late once its build finished).
|
||||
private bool _liveCenterKnown;
|
||||
// Set by the login-spawn recenter (network thread) when the spawn landblock
|
||||
// differs from the startup streaming center; consumed on the render thread
|
||||
// in OnUpdateFrame to trigger StreamingController.ForceReloadWindow(). A far
|
||||
|
|
@ -3306,6 +3315,14 @@ public sealed class GameWindow : IDisposable
|
|||
// object spawns never move the center. Idempotent + thrash-free: a
|
||||
// re-sent CreateObject for the same spawn landblock leaves the center
|
||||
// already-equal, so the guard is false on every repeat.
|
||||
//
|
||||
// #192: the player's own spawn is the first point we can trust
|
||||
// _liveCenterX/Y as a REAL position rather than the Holtburg startup
|
||||
// placeholder — set this unconditionally (even when no recenter is
|
||||
// needed, e.g. a genuine Holtburg login) so StreamingReadinessGate can
|
||||
// stop gating streaming on session state alone.
|
||||
if (spawn.Guid == _playerServerGuid)
|
||||
_liveCenterKnown = true;
|
||||
if (spawn.Guid == _playerServerGuid
|
||||
&& (lbX != _liveCenterX || lbY != _liveCenterY))
|
||||
{
|
||||
|
|
@ -8281,8 +8298,16 @@ public sealed class GameWindow : IDisposable
|
|||
// only.
|
||||
bool liveInWorld = _liveSession is not null
|
||||
&& _liveSession.CurrentState == AcDream.Core.Net.WorldSession.State.InWorld;
|
||||
// #192: liveInWorld alone used to be sufficient here — but InWorld fires
|
||||
// right after the login handshake, before the player's own spawn
|
||||
// CreateObject (and hence the real center) has arrived. Streaming that
|
||||
// ran in that gap baked landblock offsets from the still-placeholder
|
||||
// _liveCenterX/Y (Holtburg), producing stale-positioned geometry applied
|
||||
// late once its build finished. Require _liveCenterKnown too — see
|
||||
// AcDream.Core.World.StreamingReadinessGate for the full mechanism.
|
||||
if (_streamingController is not null && _cameraController is not null
|
||||
&& (!IsLiveModeWaitingForLogin || liveInWorld))
|
||||
&& AcDream.Core.World.StreamingReadinessGate.ShouldStream(
|
||||
LiveModeEnabled, _chaseModeEverEntered, liveInWorld, _liveCenterKnown))
|
||||
{
|
||||
int observerCx = _liveCenterX;
|
||||
int observerCy = _liveCenterY;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue