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();
}