diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index 329e4ad7..d238c75f 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -5470,6 +5470,12 @@ public sealed class GameWindow : IDisposable
// player position can't drift the observer off the dungeon and re-expand.
if (_streamingController is not null && IsSealedDungeonCell(p.LandblockId))
_streamingController.PreCollapseToDungeon(lbX, lbY);
+ else
+ // Outdoor teleport: the render origin (_liveCenter) just moved. Drop ALL
+ // resident terrain so overlapping blocks on a NEARBY jump re-bake at the
+ // new origin instead of rendering shifted — the confirmed "terrain in the
+ // sky" arcs (stale slots offset by exactly deltaLB×192). 2026-06-22.
+ _streamingController?.ForceReloadWindow();
}
else
{
diff --git a/src/AcDream.App/Streaming/StreamingController.cs b/src/AcDream.App/Streaming/StreamingController.cs
index 28203d55..79f1740a 100644
--- a/src/AcDream.App/Streaming/StreamingController.cs
+++ b/src/AcDream.App/Streaming/StreamingController.cs
@@ -344,6 +344,31 @@ public sealed class StreamingController
_region = rebuilt;
}
+ ///
+ /// 2026-06-22: an OUTDOOR teleport moved the render origin (_liveCenter). Every
+ /// resident terrain block was baked relative to the OLD origin, so any block that survives
+ /// an INCREMENTAL recenter — which happens on a NEARBY jump where the old and new windows
+ /// overlap, e.g. (170,168)→(169,180) — renders shifted by the jump distance: the confirmed
+ /// "terrain in the sky" arcs (the stale slots were all offset by exactly deltaLB×192).
+ /// SYNCHRONOUSLY drop every resident landblock (render slot + physics + state) so none
+ /// survives into the next frame stale, and no async unload can race a re-bake, then null
+ /// the region so the next re-bootstraps the WHOLE window fresh at
+ /// the new origin (the near ring is priority-applied behind the fade; the rest streams in).
+ /// A sealed-dungeon destination uses instead.
+ ///
+ public void ForceReloadWindow()
+ {
+ _collapsed = false;
+ // Snapshot — RemoveLandblock mutates the loaded set we're iterating.
+ var ids = new List(_state.LoadedLandblockIds);
+ foreach (var id in ids)
+ {
+ _state.RemoveLandblock(id);
+ _removeTerrain?.Invoke(id); // frees the render slot + physics + cell registries
+ }
+ _region = null; // NormalTick re-creates + bootstraps the full window next frame
+ }
+
///
/// Apply streamed completions for this frame. LOADS (terrain mesh GPU uploads) are the
/// expensive part, so they are metered at to avoid a