fix(teleport): preserve dungeon cell identity on respawn (#215)
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>
This commit is contained in:
parent
b0c175afc0
commit
0ad6700a07
7 changed files with 296 additions and 9 deletions
|
|
@ -5985,13 +5985,24 @@ public sealed class GameWindow : IDisposable
|
|||
&& _playerController.State == AcDream.App.Input.PlayerState.PortalSpace
|
||||
&& update.Guid == _playerServerGuid)
|
||||
{
|
||||
// Compute old landblock coords from controller position (using the
|
||||
// current streaming origin as the reference center).
|
||||
var oldPos = _playerController.Position;
|
||||
int oldLbX = _liveCenterX + (int)System.Math.Floor(oldPos.X / 192f);
|
||||
int oldLbY = _liveCenterY + (int)System.Math.Floor(oldPos.Y / 192f);
|
||||
uint streamingOriginLandblockId =
|
||||
AcDream.App.Streaming.StreamingRegion.EncodeLandblockId(
|
||||
_liveCenterX, _liveCenterY);
|
||||
|
||||
bool differentLandblock = (lbX != oldLbX || lbY != oldLbY);
|
||||
// Retail preserves the full Position (objcell_id + Frame) through
|
||||
// SmartBox::TeleportPlayer @ 0x00453910. Dungeon EnvCells can have
|
||||
// negative local frame origins, so XYZ cannot identify the source
|
||||
// landblock (#215). Compare the controller's authoritative current
|
||||
// cell with the received destination cell instead.
|
||||
var landblockTransition =
|
||||
AcDream.App.Streaming.TeleportLandblockTransition.Classify(
|
||||
_playerController.CellId,
|
||||
p.LandblockId,
|
||||
streamingOriginLandblockId);
|
||||
int oldLbX = (int)((landblockTransition.SourceLandblockId >> 24) & 0xFFu);
|
||||
int oldLbY = (int)((landblockTransition.SourceLandblockId >> 16) & 0xFFu);
|
||||
bool differentLandblock = landblockTransition.CrossesLandblock;
|
||||
|
||||
Console.WriteLine(
|
||||
$"live: teleport arrival — old lb=({oldLbX},{oldLbY}) " +
|
||||
|
|
@ -6013,9 +6024,7 @@ public sealed class GameWindow : IDisposable
|
|||
// position (Resolve's NO-LANDBLOCK verbatim branch, :605) until the
|
||||
// destination streams in. Only the offset-(0,0) center can collide with the
|
||||
// destination-local position, so removing it alone is sufficient.
|
||||
uint staleCenterId = AcDream.App.Streaming.StreamingRegion.EncodeLandblockId(
|
||||
_liveCenterX, _liveCenterY);
|
||||
_physicsEngine.RemoveLandblock(staleCenterId);
|
||||
_physicsEngine.RemoveLandblock(streamingOriginLandblockId);
|
||||
|
||||
// Recenter the streaming controller on the new landblock NOW (kick
|
||||
// off the dungeon load). After recentering, the destination is
|
||||
|
|
|
|||
42
src/AcDream.App/Streaming/TeleportLandblockTransition.cs
Normal file
42
src/AcDream.App/Streaming/TeleportLandblockTransition.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
namespace AcDream.App.Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Classifies whether a local-player teleport crosses an AC landblock boundary.
|
||||
/// Cell identity comes from the complete retail <c>Position</c>, never from its
|
||||
/// frame coordinates: dungeon EnvCells may have valid negative local origins.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail carries <c>Position::objcell_id</c> through
|
||||
/// <c>SmartBox::TeleportPlayer</c> (0x00453910) into
|
||||
/// <c>CPhysicsObj::SetPositionSimple</c> (0x005162B0). acdream's asynchronous
|
||||
/// streaming layer needs this extra comparison, but preserves that identity rule.
|
||||
/// See <c>docs/research/2026-07-13-same-dungeon-respawn-landblock-identity-pseudocode.md</c>.
|
||||
/// </remarks>
|
||||
internal readonly record struct TeleportLandblockTransition(
|
||||
uint SourceLandblockId,
|
||||
uint DestinationLandblockId)
|
||||
{
|
||||
public bool CrossesLandblock => SourceLandblockId != DestinationLandblockId;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static TeleportLandblockTransition Classify(
|
||||
uint sourceCellId,
|
||||
uint destinationCellId,
|
||||
uint currentStreamingCenterLandblockId)
|
||||
{
|
||||
uint sourceLandblockId = sourceCellId != 0
|
||||
? NormalizeLandblockId(sourceCellId)
|
||||
: NormalizeLandblockId(currentStreamingCenterLandblockId);
|
||||
|
||||
return new TeleportLandblockTransition(
|
||||
sourceLandblockId,
|
||||
NormalizeLandblockId(destinationCellId));
|
||||
}
|
||||
|
||||
private static uint NormalizeLandblockId(uint cellOrLandblockId)
|
||||
=> (cellOrLandblockId & 0xFFFF0000u) | 0xFFFFu;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue