diff --git a/src/AcDream.App/Streaming/GpuWorldState.cs b/src/AcDream.App/Streaming/GpuWorldState.cs index 1e7702b..0d5b647 100644 --- a/src/AcDream.App/Streaming/GpuWorldState.cs +++ b/src/AcDream.App/Streaming/GpuWorldState.cs @@ -51,10 +51,21 @@ public sealed class GpuWorldState /// Append an entity to a specific landblock's slot. Used by the live /// CreateObject path where the server spawns entities into an already- /// loaded landblock after the initial hydration pass. + /// + /// + /// The server's landblockId is in cell-resolved form + /// (0xAAAA00CC: high byte X, second byte Y, low 16 bits cell + /// index within the landblock). The streaming system stores landblocks + /// keyed by their canonical 0xAAAA0xFFFF form. Canonicalize on + /// the way in so callers don't have to think about it — masking with + /// 0xFFFF0000u | 0xFFFFu drops the cell index and selects the + /// LandBlock dat terminator. + /// /// public void AppendLiveEntity(uint landblockId, WorldEntity entity) { - if (!_loaded.TryGetValue(landblockId, out var lb)) + uint canonicalLandblockId = (landblockId & 0xFFFF0000u) | 0xFFFFu; + if (!_loaded.TryGetValue(canonicalLandblockId, out var lb)) return; // LoadedLandblock.Entities is an IReadOnlyList. Rebuild the @@ -64,7 +75,7 @@ public sealed class GpuWorldState var newEntities = new List(lb.Entities.Count + 1); newEntities.AddRange(lb.Entities); newEntities.Add(entity); - _loaded[landblockId] = new LoadedLandblock(lb.LandblockId, lb.Heightmap, newEntities); + _loaded[canonicalLandblockId] = new LoadedLandblock(lb.LandblockId, lb.Heightmap, newEntities); RebuildFlatView(); }