namespace AcDream.App.Streaming; /// /// Classifies both whether the player crosses an AC landblock boundary and /// whether acdream's asynchronous streaming origin must recenter. These are /// distinct while a teleport destination is aimed but not yet placed. Cell /// identity comes from the complete retail Position, never from its frame /// coordinates: dungeon EnvCells may have valid negative local origins. /// /// /// Retail carries Position::objcell_id through /// SmartBox::TeleportPlayer (0x00453910) into /// CPhysicsObj::SetPositionSimple (0x005162B0). acdream's asynchronous /// streaming layer needs this extra comparison, but preserves that identity rule. /// See docs/research/2026-07-13-same-dungeon-respawn-landblock-identity-pseudocode.md. /// internal readonly record struct TeleportLandblockTransition( uint SourceLandblockId, uint DestinationLandblockId, uint StreamingCenterLandblockId) { public bool CrossesLandblock => SourceLandblockId != DestinationLandblockId; public bool ChangesStreamingCenter => StreamingCenterLandblockId != DestinationLandblockId; /// /// Build the transition from the player's current cell and the received /// destination cell. A zero source can occur before initial placement; in /// that case the current streaming center is the only valid identity fallback. /// public static TeleportLandblockTransition Classify( uint sourceCellId, uint destinationCellId, uint currentStreamingCenterLandblockId) { uint sourceLandblockId = sourceCellId != 0 ? NormalizeLandblockId(sourceCellId) : NormalizeLandblockId(currentStreamingCenterLandblockId); return new TeleportLandblockTransition( sourceLandblockId, NormalizeLandblockId(destinationCellId), NormalizeLandblockId(currentStreamingCenterLandblockId)); } private static uint NormalizeLandblockId(uint cellOrLandblockId) => (cellOrLandblockId & 0xFFFF0000u) | 0xFFFFu; }