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