fix(streaming): relocate player entity to current landblock every frame
TWO root causes for "character disappears when walking far": 1. MarkPersistent stored SERVER GUID but RemoveLandblock checked LOCAL entity.Id — different namespaces, never matched. Fixed by adding WorldEntity.ServerGuid field and checking it in RemoveLandblock. 2. Even with rescue working, the player entity stays in its SPAWN landblock's entity list forever. When the player walks to a new landblock and the spawn landblock gets frustum-culled, the entity disappears because neverCullLandblockId is computed from the player's current position (new landblock) but the entity is stored in the old landblock. Fixed by calling GpuWorldState.RelocateEntity every frame in the player-mode update loop. This moves the entity from whatever landblock it's currently in to the one matching its actual position. The scan is O(entities) but only runs for one entity per frame. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c32cef7e87
commit
08e309c357
2 changed files with 51 additions and 6 deletions
|
|
@ -1713,13 +1713,18 @@ public sealed class GameWindow : IDisposable
|
|||
if (_entitiesByServerGuid.TryGetValue(_playerServerGuid, out var pe))
|
||||
{
|
||||
pe.Position = result.Position;
|
||||
// AC character models face +Y in their default orientation.
|
||||
// Our yaw convention has cos(yaw)=+X at yaw=0, so yaw=0
|
||||
// means facing +X. Offset by -PI/2 so the model faces the
|
||||
// actual walk direction (at yaw=0, model rotation = -PI/2
|
||||
// = facing +X instead of the model's default +Y).
|
||||
pe.Rotation = System.Numerics.Quaternion.CreateFromAxisAngle(
|
||||
System.Numerics.Vector3.UnitZ, _playerController.Yaw - MathF.PI / 2f);
|
||||
|
||||
// Move the player entity to its current landblock in GpuWorldState
|
||||
// so it doesn't get frustum-culled when the player walks away from
|
||||
// the spawn landblock. Without this, the entity stays in the spawn
|
||||
// landblock's entity list and disappears when that landblock is culled.
|
||||
var pp = _playerController.Position;
|
||||
int plx = _liveCenterX + (int)System.Math.Floor(pp.X / 192f);
|
||||
int ply = _liveCenterY + (int)System.Math.Floor(pp.Y / 192f);
|
||||
uint currentLb = (uint)((plx << 24) | (ply << 16) | 0xFFFF);
|
||||
_worldState.RelocateEntity(pe, currentLb);
|
||||
}
|
||||
|
||||
// Update chase camera.
|
||||
|
|
|
|||
|
|
@ -123,7 +123,45 @@ public sealed class GpuWorldState
|
|||
/// Mark a server-GUID as persistent — this entity survives landblock unloads
|
||||
/// and gets re-parked as pending for its current canonical landblock.
|
||||
/// </summary>
|
||||
public void MarkPersistent(uint serverGuid) => _persistentGuids.Add(serverGuid);
|
||||
public void MarkPersistent(uint serverGuid)
|
||||
{
|
||||
_persistentGuids.Add(serverGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a persistent entity from its current landblock slot to a new one.
|
||||
/// Called every frame for the player entity so it stays in the landblock
|
||||
/// matching its actual position (not its spawn landblock). Without this,
|
||||
/// the entity stays in the spawn landblock and gets frustum-culled when
|
||||
/// the player walks away.
|
||||
/// </summary>
|
||||
public void RelocateEntity(WorldEntity entity, uint newCanonicalLb)
|
||||
{
|
||||
if (entity.ServerGuid == 0) return;
|
||||
|
||||
// Remove from current landblock (find it by scanning)
|
||||
foreach (var kvp in _loaded)
|
||||
{
|
||||
var entities = kvp.Value.Entities;
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
{
|
||||
if (ReferenceEquals(entities[i], entity))
|
||||
{
|
||||
if (kvp.Key == newCanonicalLb) return; // already in the right place
|
||||
|
||||
// Remove from old
|
||||
var newList = new List<WorldEntity>(entities.Count - 1);
|
||||
for (int j = 0; j < entities.Count; j++)
|
||||
if (j != i) newList.Add(entities[j]);
|
||||
_loaded[kvp.Key] = new LoadedLandblock(kvp.Value.LandblockId, kvp.Value.Heightmap, newList);
|
||||
|
||||
// Add to new (via AppendLiveEntity which handles pending)
|
||||
AppendLiveEntity(newCanonicalLb, entity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveLandblock(uint landblockId)
|
||||
{
|
||||
|
|
@ -136,7 +174,9 @@ public sealed class GpuWorldState
|
|||
foreach (var entity in lb.Entities)
|
||||
{
|
||||
if (entity.ServerGuid != 0 && _persistentGuids.Contains(entity.ServerGuid))
|
||||
{
|
||||
_persistentRescued.Add(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue