#126 RETAIL-CORRECTED: restores commit the server Z - retail never re-derives position from surfaces

The user caught the process failure: two snap fixes were written without
reading retails restore code. The named decomp settles it -
CPhysicsObj::SetPositionInternal (0x00515bd0, pc:283892-283945) treats
the supplied Position as INPUT: AdjustPosition resolves which cell
CONTAINS it, CheckPositionInternal/find_valid_position VALIDATES it
through the collision transition, and the no-cell case goes
store_position + GotoLostCell. There is NO terrain or surface
re-grounding anywhere in the restore path. Trust + validate.

Both prior shapes diverged: grounding outdoor claims to terrainZ warped
a roof-deck logout (ACEs authoritative z=127.2 on the AAB3 tower)
through the roof into the building volume -> the transparent-interior
spawn on every login; the cell-walkable scan that replaced it missed
shell-geometry decks entirely (no EnvCell owns the deck surface) and
failed silently - the user logged in transparent at the tower bottom
again.

Fix: a zero-delta outdoor restore above terrain commits the claims Z
verbatim ([snap] line says so); the first physics tick validates and
settles against the REAL collision world (the BR-7 building channel
covers the deck). max(terrain, z) stays as the under-terrain sanity
bound - our recoverable stand-in for retails lost-cell machinery
(documented divergence, same class as the #107 demote).

Suites: App 242+1skip, Core 1422+2skip, UI 420, Net 294.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-11 20:22:17 +02:00
parent b94a7e8017
commit 120aeff720
2 changed files with 54 additions and 50 deletions

View file

@ -156,23 +156,39 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
if (_isUninitialized || _meshManager is null) return;
_meshManager.IncrementRefCount(id);
if (_metadataPopulated.Add(id))
{
bool firstEver = _metadataPopulated.Add(id);
if (firstEver)
PopulateMetadata(id);
// WB's IncrementRefCount alone only bumps a usage counter; it does
// NOT trigger mesh loading. We must explicitly call PrepareMeshDataAsync
// so the background workers actually decode the GfxObj. The result
// auto-enqueues into _stagedMeshData (ObjectMeshManager line 510),
// which Tick() drains onto the GPU. Until that completes,
// TryGetRenderData(id) returns null and the dispatcher silently
// skips the entity — standard streaming flicker.
//
// isSetup: false — acdream's MeshRefs already carry expanded
// per-part GfxObj ids (0x01XXXXXX). WB's Setup-expansion path is
// unused.
// WB's IncrementRefCount alone only bumps a usage counter; it does
// NOT trigger mesh loading. We must explicitly call PrepareMeshDataAsync
// so the background workers actually decode the GfxObj. The result
// auto-enqueues into _stagedMeshData (ObjectMeshManager line 510),
// which Tick() drains onto the GPU. Until that completes,
// TryGetRenderData(id) returns null and the dispatcher silently
// skips the entity — standard streaming flicker.
//
// #128 (2026-06-11): Prepare must RE-ARM whenever the id has no render
// data — NOT only on the first-ever registration. The old
// first-ever-only gate (`if (_metadataPopulated.Add(id))`) permanently
// lost any id whose initial decode was cancelled before completing
// (landblock unload → CancelStagedUploads during login/teleport
// churn) or whose upload was later LRU-evicted: every subsequent
// registration skipped Prepare, so the mesh stayed invisible for the
// session with zero log output — the dispatcher's slow path just
// counted meshMissing forever (issue #55's 1.45M/5s mountain was this
// bug's heartbeat). User-visible: the AAB3 tower staircase rendering
// partially or not at all depending on the session's landblock
// load/unload interleaving (#119/#128 "broken stairs"). Safe to call
// unconditionally when data is absent: PrepareMeshDataAsync early-outs
// on existing render data, returns the in-flight task when already
// pending, and dedups via _preparationTasks.
//
// isSetup: false — acdream's MeshRefs already carry expanded
// per-part GfxObj ids (0x01XXXXXX). WB's Setup-expansion path is
// unused.
if (firstEver || _meshManager.TryGetRenderData(id) is null)
_meshManager.PrepareMeshDataAsync(id, isSetup: false);
}
}
/// <inheritdoc/>