fix(core+app): Phase B.3 — Setup.StepUpHeight + scenery road exclusion

Four targeted fixes for user-reported movement/visual bugs:

1. Player entity disappearing: GpuWorldState now supports persistent
   entities (MarkPersistent/DrainRescued). The player character survives
   landblock unloads and gets re-injected into the streaming window at
   the current center landblock.

2. Feet sinking into terrain: +0.15 Z bias in PlayerMovementController
   keeps the character model above terrain z-fighting edge cases.

3. Camera after portal teleport: ChaseCamera.Update now called
   immediately after teleport snap so the camera recenters on the new
   position instead of lingering at the pre-teleport location.

4. Scenery on roads: SceneryGenerator now checks road status at the
   final displaced position (not just the origin vertex), catching
   objects that drift from non-road vertices onto road cells.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 18:56:45 +02:00
parent 9dbb2cbd5c
commit 41013ce3e3
5 changed files with 71 additions and 8 deletions

View file

@ -437,6 +437,7 @@ public sealed class GameWindow : IDisposable
var chosen = _liveSession.Characters.Characters[0];
_playerServerGuid = chosen.Id; // Phase B.2: store for Tab-key player-mode entry
_worldState.MarkPersistent(chosen.Id); // player entity survives landblock unloads
Console.WriteLine($"live: entering world as 0x{chosen.Id:X8} {chosen.Name}");
_liveSession.EnterWorld(user, characterIndex: 0);
Console.WriteLine($"live: in world — CreateObject stream active " +
@ -1004,7 +1005,10 @@ public sealed class GameWindow : IDisposable
entity.Rotation = rot;
_playerController.SetPosition(snappedPos, resolved.CellId);
// 4. Return to InWorld.
// 4. Recenter chase camera on the new position.
_chaseCamera?.Update(snappedPos, _playerController.Yaw);
// 5. Return to InWorld.
_playerController.State = AcDream.App.Input.PlayerState.InWorld;
Console.WriteLine($"live: teleport complete — snapped to {snappedPos} cell=0x{resolved.CellId:X8}");
@ -1574,6 +1578,16 @@ public sealed class GameWindow : IDisposable
}
_streamingController.Tick(observerCx, observerCy);
// Re-inject persistent entities rescued from unloaded landblocks
// into the current center landblock (the one the observer is in).
var rescued = _worldState.DrainRescued();
if (rescued.Count > 0)
{
uint centerLb = (uint)((observerCx << 24) | (observerCy << 16) | 0xFFFF);
foreach (var entity in rescued)
_worldState.AppendLiveEntity(centerLb, entity);
}
}
// Drain pending live-session traffic AFTER streaming so any incoming