feat(headless): hydrate isolated collision worlds

This commit is contained in:
Erik 2026-07-27 09:25:58 +02:00
parent 9569dadb57
commit b6547ff38c
20 changed files with 1960 additions and 101 deletions

View file

@ -972,7 +972,7 @@ public sealed class PlayerMovementController
/// Install a complete authoritative frame rotation (spawn, teleport, or
/// server correction) without collapsing it through the yaw projection.
/// </summary>
internal void SetBodyOrientation(Quaternion orientation)
public void SetBodyOrientation(Quaternion orientation)
{
_body.Orientation = AcDream.Core.Physics.Motion.FrameOps.SetRotate(
_body.Position,

View file

@ -0,0 +1,24 @@
namespace AcDream.Runtime.Gameplay;
/// <summary>
/// Applies the exact server-owned run/jump snapshot to either host's one local
/// movement controller. This lives beside the canonical skill owner so
/// graphical and no-window construction cannot drift.
/// </summary>
public static class RuntimeMovementSkillProjection
{
public static bool ApplyTo(
RuntimeMovementSkillState skills,
PlayerMovementController? controller)
{
ArgumentNullException.ThrowIfNull(skills);
RuntimeMovementSkillSnapshot snapshot = skills.Snapshot;
if (controller is null || !snapshot.IsComplete)
return false;
controller.SetCharacterSkills(
snapshot.RunSkill,
snapshot.JumpSkill);
return true;
}
}

View file

@ -7,6 +7,23 @@ using AcDream.Runtime.World;
namespace AcDream.Runtime.Session;
public interface IRuntimeDirectWorldProjection
{
void ProjectSpawn(
RuntimeEntityRecord record,
bool isLocalPlayer);
void ProjectPosition(
RuntimeEntityRecord record,
bool isLocalPlayer);
void BeginTeleport();
RuntimeDestinationReadiness PrepareDestination(
long revealGeneration,
RuntimeTeleportDestination destination);
}
/// <summary>
/// Presentation-free inbound entity route for a direct Runtime host. It
/// applies the same canonical identity, timestamp, object-table, and transit
@ -18,15 +35,18 @@ public sealed class RuntimeLiveEntitySessionController
private readonly GameRuntime _runtime;
private readonly WorldSession _session;
private readonly Action<string> _log;
private readonly IRuntimeDirectWorldProjection? _worldProjection;
public RuntimeLiveEntitySessionController(
GameRuntime runtime,
WorldSession session,
Action<string>? log = null)
Action<string>? log = null,
IRuntimeDirectWorldProjection? worldProjection = null)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_session = session ?? throw new ArgumentNullException(nameof(session));
_log = log ?? (_ => { });
_worldProjection = worldProjection;
}
public LiveEntitySessionSink CreateSink() => new(
@ -54,13 +74,20 @@ public sealed class RuntimeLiveEntitySessionController
return;
ulong integrationVersion = canonical.CreateIntegrationVersion;
_ = Entities.ApplyAcceptedSpawn(
bool applied = Entities.ApplyAcceptedSpawn(
canonical,
integrationVersion,
canonical.Snapshot,
replaceGeneration:
registration.Inbound.Disposition
is CreateObjectTimestampDisposition.NewGeneration);
if (applied)
{
_worldProjection?.ProjectSpawn(
canonical,
canonical.ServerGuid
== _runtime.PlayerIdentity.ServerGuid);
}
}
private void OnDeleted(DeleteObject.Parsed delete)
@ -146,6 +173,14 @@ public sealed class RuntimeLiveEntitySessionController
_runtime.TransitOwner.OfferTeleportDestination(
destination,
timestamps.TeleportAdvanced);
if (Entities.Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord record))
{
_worldProjection?.ProjectPosition(
record,
isLocalPlayer: true);
}
TryCompletePortal();
}
@ -174,6 +209,7 @@ public sealed class RuntimeLiveEntitySessionController
RuntimeWorldTransitState transit = _runtime.TransitOwner;
if (!transit.TryQueueTeleportStart(sequence))
return;
_worldProjection?.BeginTeleport();
if (!transit.ActivateQueuedTeleport())
{
throw new InvalidOperationException(
@ -216,16 +252,21 @@ public sealed class RuntimeLiveEntitySessionController
RuntimeWorldHostAcknowledgementStage.ProjectionRegistered);
bool indoor = (destination.CellId & 0xFFFFu) >= 0x0100u;
RuntimeDestinationReadiness readiness =
_worldProjection?.PrepareDestination(
generation,
destination)
?? new RuntimeDestinationReadiness(
generation,
destination.CellId,
indoor,
IsUnhydratable: false,
RequiredRenderRadius: indoor ? 0 : 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true);
if (!transit.AcknowledgeDestinationReadiness(
new RuntimeDestinationReadiness(
generation,
destination.CellId,
indoor,
IsUnhydratable: false,
RequiredRenderRadius: indoor ? 0 : 1,
IsRenderNeighborhoodReady: true,
AreCompositeTexturesReady: true,
IsCollisionReady: true)))
readiness))
{
throw new InvalidOperationException(
"Runtime rejected headless destination readiness.");