Classify teleport landblock transitions from the source and destination Position cell IDs instead of render-space coordinates. This keeps same-dungeon death respawns on their resident floor physics while preserving the existing true cross-landblock streaming path. Co-Authored-By: Codex <noreply@openai.com>
4.4 KiB
Same-dungeon respawn landblock identity — retail pseudocode
Symptom and captured cause
After /die in the starter dungeon, ACE respawned the player in the same cell,
0x8C0401AD. The player stood correctly until the first movement input, then
lost floor contact and fell through the world.
The destination update and the player controller both named cell
0x8C0401AD, but acdream reconstructed the source landblock from render-space
XYZ. The dungeon cell's valid local Y was about -30.4 m, so this expression:
sourceLbY = streamingCenterY + floor(renderY / 192 m)
= 4 + floor(-30.4 / 192)
= 3
misidentified the source as landblock (140,3). The destination was (140,4),
so the same-cell respawn entered the cross-landblock branch, removed
0x8C04FFFF from the physics engine, and left the player with no hydrated floor.
Dungeon EnvCells may have arbitrary negative frame origins. Their XYZ values therefore cannot identify an AC landblock.
Named retail oracle
Retail stores cell identity inside every position:
// acclient.h:30658-30662
struct Position : PackObj
{
unsigned int objcell_id;
Frame frame;
};
The local-player teleport path preserves that complete value end to end:
SmartBox::TeleportPlayer @ 0x00453910passes itsPosition const*directly toCPhysicsObj::SetPositionSimple.CPhysicsObj::SetPositionSimple @ 0x005162B0copies that completePositioninto aSetPositionStruct, then callsCPhysicsObj::SetPosition.SmartBox::HandleReceivedPosition's local-player teleport branch (0x0045415F–0x00454168) passes the receivedPositiontoSmartBox::TeleportPlayer; it does not derive a cell from XYZ.
References:
docs/research/named-retail/acclient.h:30658docs/research/named-retail/acclient_2013_pseudo_c.txt:92514docs/research/named-retail/acclient_2013_pseudo_c.txt:93013docs/research/named-retail/acclient_2013_pseudo_c.txt:284276
Reference-client/server cross-check
The existing L.3 research traces the same contract through two independent implementations:
- ACE sends death respawn as
GameMessagePlayerTeleport, immediately followed by anUpdatePositionwhosePositionPackcontains the destination cell and frame (docs/research/2026-05-04-l3-port/12-hard-teleport-branch.md, sections 7 and 9). - The retail packet path unpacks
PositionPack.positionasobjcell_id + Frameand routes that complete value into the hard-teleport branch (docs/research/2026-05-04-l3-port/03-up-routing.md, sections 0 and 3).
The repository's old external ACE checkout is no longer present after the WorldBuilder extraction, so the checked-in cross-reference research is the stable ACE source citation for this change.
Faithful pseudocode
Retail has synchronous cell access and can install the received Position
directly. acdream keeps its asynchronous loading cover, but the identity
decision must use the same Position.objcell_id values:
on local player teleport destination(receivedPosition):
sourceCell = player.currentPosition.objcell_id
destinationCell = receivedPosition.objcell_id
if sourceCell is zero:
# acdream-only pre-placement fallback; no XYZ inference
sourceLandblock = currentStreamingCenterLandblock
else:
sourceLandblock = landblockOf(sourceCell)
destinationLandblock = landblockOf(destinationCell)
crossesLandblock = sourceLandblock != destinationLandblock
if crossesLandblock:
evict the old streaming-origin physics landblock
recenter streaming on destinationLandblock
start the existing destination hydration path
retain receivedPosition.objcell_id as the pending destination cell
when the existing readiness gate opens:
place the player with the complete destination cell + frame
landblockOf(cell) means (cell & 0xFFFF0000) | 0xFFFF. At no point does
the decision divide or floor position coordinates.
Conformance cases
- Source and destination are both
0x8C0401AD, while render Y is negative: same landblock; do not evict or recenter. - Source and destination are different EnvCells in
0x8C04: same landblock; do not evict or recenter. - Source
0x8C0401AD, destination0xA9B40022: cross-landblock; retain the existing recenter/hydration behavior. - Source cell is zero before first placement: compare against the current streaming-center ID; never infer from XYZ.