V2/Runtime/HexPlayerBody.cs

Client-owned player body component with host-authoritative counterpart and local prediction. Manages authoritative body creation/activation, input submission RPC, host input application, prediction, reconciliation, camera modification, and lifecycle of prepared/retired bodies.

NetworkingFile AccessHttp Calls
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Hexagon.V2.Kernel;
using Hexagon.V2.Networking;
using Sandbox;

namespace Hexagon.V2.Runtime;

/// <summary>Host-authored movement state tied to one processed input sequence.</summary>
public readonly record struct PlayerMovementState(
	int BodyGeneration,
	uint ProcessedInputSequence,
	Vector3 Position,
	Vector3 Velocity,
	float Pitch,
	float Yaw,
	bool IsGrounded,
	bool IsDucking )
{
	public bool IsFinite =>
		float.IsFinite( Position.x ) && float.IsFinite( Position.y ) && float.IsFinite( Position.z ) &&
		float.IsFinite( Velocity.x ) && float.IsFinite( Velocity.y ) && float.IsFinite( Velocity.z ) &&
		float.IsFinite( Pitch ) && float.IsFinite( Yaw );
}

/// <summary>
/// Client-owned identity/input shell. The playable body is an unowned network
/// object simulated by the host; an owner-local, never-networked predictor is
/// presentation only and is never exposed to authority callers.
/// </summary>
public sealed class HexPlayerBody : Component, ICameraModifier, IRuntimePlayer
{
	private const float InputSendIntervalSeconds = 1f / 30f;
	private const float PredictionSmoothSeconds = 0.100f;
	private const int PredictionHistoryCapacity = 128;

	private readonly PlayerInputAdmission _inputAdmission = new();
	private readonly Queue<PredictedFrameSnapshot> _predictionHistory = new();
	private readonly List<GameObject> _retiredBodies = new();
	private PreparedAuthoritativeBody? _preparedBody;
	private bool _stripRequestedDuringPreparation;
	private Transform _hostSpawnTransform;
	private PlayerInputFrame _latestHostInput;
	private bool _hasLatestHostInput;
	private double _lastAcceptedInputSeconds;
	private uint _lastAppliedJumpSequence;
	private bool _hasAppliedJumpSequence;
	private double _lastJumpSeconds = double.NegativeInfinity;
	private GameObject? _predictedBody;
	private PlayerController? _predictedController;
	private GameObject? _presentedAuthoritativeBody;
	private int _predictedGeneration = -1;
	private uint _nextInputSequence;
	private uint _nextJumpSequence;
	private bool _pendingJump;
	private uint _lastReconciledSequence;
	private float _inputSendAccumulator;
	private Vector3 _smoothCorrection;
	private int _localGeneration = -1;
	private Angles _localEyeAngles;
	private bool _localLookInitialized;
	private bool _localThirdPerson = true;
	private float _cameraDistance = 20f;

	[Sync( SyncFlags.FromHost )] public Guid ConnectionGuid { get; private set; }
	[Sync( SyncFlags.FromHost )] public ulong PlatformAccountDisplay { get; private set; }
	[Sync( SyncFlags.FromHost )] public string PlatformDisplayName { get; private set; } = string.Empty;
	[Sync( SyncFlags.FromHost )] public Guid CharacterGuid { get; private set; }
	[Sync( SyncFlags.FromHost )] public string CharacterName { get; private set; } = string.Empty;
	[Sync( SyncFlags.FromHost )] public string CharacterDescription { get; private set; } = string.Empty;
	[Sync( SyncFlags.FromHost )] public string CharacterModel { get; private set; } = string.Empty;
	[Sync( SyncFlags.FromHost )] public string FactionId { get; private set; } = string.Empty;
	[Sync( SyncFlags.FromHost )] public string ClassId { get; private set; } = string.Empty;
	[Sync( SyncFlags.FromHost )] public bool HasActiveCharacter { get; private set; }
	[Sync( SyncFlags.FromHost )] public bool IsDead { get; private set; }
	[Sync( SyncFlags.FromHost )] public bool IsWeaponRaised { get; private set; }
	[Sync( SyncFlags.FromHost )] public GameObject? AuthoritativeBody { get; private set; }
	[Sync( SyncFlags.FromHost )] public int BodyGeneration { get; private set; }
	[Sync( SyncFlags.FromHost )] public bool HasProcessedInput { get; private set; }
	[Sync( SyncFlags.FromHost )] public uint ProcessedInputSequence { get; private set; }
	[Sync( SyncFlags.FromHost )] public PlayerMovementState ProcessedMovementState { get; private set; }

	internal Connection? HostConnection { get; set; }
	internal Func<HexPlayerBody, PlayerInputFrame, bool>? HostInputAuthenticator { get; set; }

	public bool TryGetUsableAuthoritativeBody( out GameObject body )
	{
		body = AuthoritativeBody!;
		return PlayerBodyAuthorityRules.IsUsable(
			HasActiveCharacter,
			IsDead,
			body is not null && body.IsValid(),
			body is not null && body.IsValid() && body.Enabled );
	}

	/// <summary>
	/// Identifies the engine input controller created by this shell for local-only
	/// prediction. Callers must still submit commands to host authority; this is
	/// only a presentation/input-source check.
	/// </summary>
	public static bool IsLocalPredictionSource( Component? source ) =>
		(source is PlayerController &&
			source.Components.Get<HexLocalPredictionMarker>() is { } marker &&
			marker.IsCurrent( source )) ||
		(source is HexPlayerBody shell && shell.IsOwnerLocal);

	private bool IsOwnerLocal => GameObject.Network.Active && GameObject.Network.IsOwner;

	internal void HostSetConnection( Connection connection )
	{
		HostConnection = connection;
		_hostSpawnTransform = GameObject.WorldTransform.WithScale( 1 );
		ConnectionGuid = connection.Id;
		PlatformAccountDisplay = connection.SteamId.ValueUnsigned;
		PlatformDisplayName = connection.DisplayName;
	}

	internal void HostApplyPublicSnapshot( PlayerPublicSnapshot snapshot )
	{
		ConnectionGuid = snapshot.ConnectionId.Value;
		PlatformAccountDisplay = snapshot.PlatformAccountId;
		PlatformDisplayName = snapshot.PlatformDisplayName;
		CharacterGuid = snapshot.CharacterId?.Value ?? Guid.Empty;
		CharacterName = snapshot.ReplicatedCharacterName;
		CharacterDescription = snapshot.Description;
		CharacterModel = snapshot.Model?.Value ?? string.Empty;
		FactionId = snapshot.Faction?.Value ?? string.Empty;
		ClassId = snapshot.Class?.Value ?? string.Empty;
		HasActiveCharacter = snapshot.HasCharacter;
		IsDead = snapshot.IsDead;
		IsWeaponRaised = snapshot.IsWeaponRaised;
	}

	/// <summary>
	/// Creates and atomically activates an unowned, host-simulated body.
	/// </summary>
	public OperationResult<GameObject> HostBuildAuthoritativeBody( Action<GameObject> configure )
	{
		var prepared = HostPrepareAuthoritativeBody( configure );
		if ( prepared.Failed )
			return OperationResult<GameObject>.Failure(
				prepared.Error!.Code, prepared.Error.Message, prepared.Error.Details );
		return prepared.Value.TryActivate();
	}

	/// <summary>
	/// Fully configures and network-spawns a disabled replacement while preserving
	/// the current body. The initial transform comes only from host-held state: the
	/// previous canonical body or the original host-selected spawn transform.
	/// </summary>
	public OperationResult<PreparedAuthoritativeBody> HostPrepareAuthoritativeBody( Action<GameObject> configure )
	{
		ArgumentNullException.ThrowIfNull( configure );
		if ( !Sandbox.Networking.IsHost )
			return OperationResult<PreparedAuthoritativeBody>.Failure(
				ErrorCode.Unauthorized, "Only the host can prepare an authoritative body." );
		if ( HostConnection is null )
			return OperationResult<PreparedAuthoritativeBody>.Failure(
				ErrorCode.NotFound, "The player connection is unavailable." );
		if ( _preparedBody is not null )
			return OperationResult<PreparedAuthoritativeBody>.Failure(
				ErrorCode.Conflict, "An authoritative body replacement is already prepared." );

		var previous = AuthoritativeBody is not null && AuthoritativeBody.IsValid()
			? AuthoritativeBody
			: null;
		var trustedTransform = previous?.WorldTransform ?? _hostSpawnTransform;
		var candidate = new GameObject( false, "Hexagon Authoritative Body" )
		{
			WorldTransform = trustedTransform.WithScale( 1 )
		};
		try
		{
			configure( candidate );
			candidate.WorldTransform = trustedTransform.WithScale( 1 );
			var controller = candidate.Components.Get<PlayerController>( FindMode.EverythingInSelf );
			if ( controller is null )
				throw new InvalidOperationException( "An authoritative body requires a PlayerController." );
			controller.UseInputControls = false;
			controller.UseLookControls = false;
			controller.UseCameraControls = false;
			controller.EnablePressing = false;
			var spawned = candidate.NetworkSpawn( new NetworkSpawnOptions
			{
				Owner = null!,
				StartEnabled = false,
				OwnerTransfer = OwnerTransfer.Fixed,
				OrphanedMode = NetworkOrphaned.Destroy
			} );
			if ( !spawned )
			{
				DestroyOrRetain( candidate, "rejected replacement" );
				return OperationResult<PreparedAuthoritativeBody>.Failure(
					ErrorCode.InternalError, "The authoritative body could not be network-spawned." );
			}
			var prepared = new PreparedAuthoritativeBody( this, candidate, previous );
			_preparedBody = prepared;
			return OperationResult<PreparedAuthoritativeBody>.Success( prepared );
		}
		catch ( Exception exception )
		{
			DestroyOrRetain( candidate, "failed replacement" );
			Log.Error( exception, "Hexagon schema failed to compose an authoritative body." );
			return OperationResult<PreparedAuthoritativeBody>.Failure(
				ErrorCode.InternalError, "The authoritative body could not be composed." );
		}
	}

	public OperationResult HostStripAuthoritativeBody()
	{
		if ( !Sandbox.Networking.IsHost )
			return OperationResult.Failure( ErrorCode.Unauthorized, "Only the host can strip an authoritative body." );
		var publishRemoval = !Game.IsClosing;
		if ( _preparedBody is not null )
		{
			_stripRequestedDuringPreparation = true;
			try
			{
				if ( AuthoritativeBody is not null && AuthoritativeBody.IsValid() )
				{
					AuthoritativeBody.Enabled = false;
					if ( publishRemoval ) AuthoritativeBody.Network.Refresh();
				}
				return OperationResult.Success();
			}
			catch ( Exception exception )
			{
				Log.Error( exception, "Hexagon could not disable an authoritative body while replacement was pending." );
				return OperationResult.Failure(
					ErrorCode.InternalError, "The authoritative body could not be disabled while replacement was pending." );
			}
		}
		return DestroyAuthoritativeBody( publishRemoval );
	}

	protected override void OnUpdate()
	{
		if ( IsOwnerLocal )
		{
			EnsureLocalGeneration();
			UpdateLocalPresentationInput();
			if ( Sandbox.Networking.IsHost ) UpdateListenHostInteraction();
		}
		UpdatePredictionPresentation();
	}

	protected override void OnFixedUpdate()
	{
		if ( Sandbox.Networking.IsHost )
		{
			CleanupRetiredBodies();
			PublishCompletedHostMovement();
			ApplyHostInput();
		}
		if ( IsOwnerLocal )
		{
			EnsureLocalGeneration();
			if ( Sandbox.Networking.IsHost )
			{
				if ( _predictedBody is not null ) DestroyPredictedBody();
			}
			else EnsurePredictedBody();
			CaptureAndSendInput();
			if ( !Sandbox.Networking.IsHost )
			{
				ReconcilePrediction();
				ApplySmoothCorrection();
			}
		}
		else if ( _predictedBody is not null )
		{
			DestroyPredictedBody();
		}
	}

	[Rpc.Host( NetFlags.OwnerOnly | NetFlags.UnreliableNoDelay )]
	private void SubmitInputFrame( PlayerInputFrame frame )
	{
		var now = MonotonicSeconds();
		if ( !_inputAdmission.TryBeginAttempt( now, out _ ) ) return;
		if ( !Sandbox.Networking.IsHost || HostConnection is null || Rpc.Caller.Id != HostConnection.Id ) return;
		if ( HostInputAuthenticator is null || !HostInputAuthenticator( this, frame ) ) return;
		if ( !TryGetUsableAuthoritativeBody( out _ ) ) return;
		if ( !_inputAdmission.TryAcceptCharged(
			frame, BodyGeneration, out var accepted, out _ ) ) return;
		_latestHostInput = accepted;
		_hasLatestHostInput = true;
		_lastAcceptedInputSeconds = now;
	}

	private void ApplyHostInput()
	{
		if ( !TryGetUsableAuthoritativeBody( out var body ) )
		{
			if ( AuthoritativeBody is { } unavailable && unavailable.IsValid() &&
				unavailable.Components.Get<PlayerController>() is { } unavailableController )
				unavailableController.WishVelocity = Vector3.Zero;
			return;
		}
		var controller = body.Components.Get<PlayerController>();
		if ( controller is null ) return;
		var now = MonotonicSeconds();
		if ( !PlayerInputAuthorityRules.IsInputFresh(
			_hasLatestHostInput, _lastAcceptedInputSeconds, now ) )
		{
			controller.WishVelocity = Vector3.Zero;
			return;
		}

		var input = _latestHostInput;
		controller.EyeAngles = new Angles( input.Pitch, input.Yaw, 0 );
		var wantsDuck = input.Buttons.HasFlag( PlayerInputButtons.Duck );
		controller.UpdateDucking( wantsDuck );
		var wantsRun = input.Buttons.HasFlag( PlayerInputButtons.Run );
		if ( controller.RunByDefault ) wantsRun = !wantsRun;
		var speed = wantsDuck
			? controller.DuckedSpeed
			: wantsRun ? controller.RunSpeed : controller.WalkSpeed;
		var localMove = new Vector3( input.MoveX, input.MoveY, 0 );
		controller.WishVelocity = Rotation.FromYaw( input.Yaw ) * localMove * speed;

		if ( input.Buttons.HasFlag( PlayerInputButtons.Jump ) &&
			(!_hasAppliedJumpSequence || PlayerInputAdmission.IsNewer( input.JumpSequence, _lastAppliedJumpSequence )) )
		{
			_lastAppliedJumpSequence = input.JumpSequence;
			_hasAppliedJumpSequence = true;
			if ( PlayerInputAuthorityRules.CanJump(
				controller.IsOnGround, controller.JumpSpeed, _lastJumpSeconds, now ) )
			{
				_lastJumpSeconds = now;
				controller.Jump( Vector3.Up * controller.JumpSpeed );
			}
		}
		ProcessedInputSequence = input.Sequence;
		HasProcessedInput = true;
	}

	private void PublishCompletedHostMovement()
	{
		if ( !HasProcessedInput || !TryGetUsableAuthoritativeBody( out var body ) ) return;
		var controller = body.Components.Get<PlayerController>();
		if ( controller is null ) return;
		var eye = controller.EyeAngles;
		var snapshot = new PlayerMovementState(
			BodyGeneration,
			ProcessedInputSequence,
			body.WorldPosition,
			controller.Velocity,
			eye.pitch,
			PlayerInputAdmission.NormalizeYaw( eye.yaw ),
			controller.IsOnGround,
			controller.IsDucking );
		if ( snapshot.IsFinite ) ProcessedMovementState = snapshot;
	}

	private void EnsurePredictedBody()
	{
		if ( Sandbox.Networking.IsHost )
		{
			DestroyPredictedBody();
			return;
		}
		var authority = AuthoritativeBody;
		if ( authority is null || !authority.IsValid() || !authority.Enabled || !HasActiveCharacter || IsDead )
		{
			DestroyPredictedBody();
			return;
		}
		if ( _predictedBody is not null && _predictedBody.IsValid() &&
			_predictedGeneration == BodyGeneration ) return;

		DestroyPredictedBody();
		var predictor = new GameObject( false, "Hexagon Predicted Body" )
		{
			NetworkMode = NetworkMode.Never,
			WorldTransform = authority.WorldTransform.WithScale( 1 )
		};
		predictor.Tags.Add( "prediction" );
		var controller = predictor.AddComponent<PlayerController>();
		controller.BodyCollisionTags = new TagSet();
		controller.BodyCollisionTags.Add( "prediction" );
		controller.CameraCollisionIgnore.Add( "playerclip" );
		controller.UseCameraControls = false;
		controller.UseAnimatorControls = false;
		controller.EnableFootstepSounds = false;
		predictor.AddComponent<HexLocalPredictionMarker>().Bind( this, BodyGeneration );
		var authoritativeController = authority.Components.Get<PlayerController>();
		if ( authoritativeController is not null ) controller.EyeAngles = authoritativeController.EyeAngles;
		predictor.Enabled = true;
		_predictedBody = predictor;
		_predictedController = controller;
		_predictedGeneration = BodyGeneration;
	}

	private void CaptureAndSendInput()
	{
		if ( !TryGetUsableAuthoritativeBody( out _ ) ) return;
		if ( Input.Pressed( "Jump" ) )
		{
			_nextJumpSequence++;
			_pendingJump = true;
		}
		_inputSendAccumulator += Math.Clamp( Time.Delta, 0f, 0.1f );
		if ( _inputSendAccumulator < InputSendIntervalSeconds ) return;
		_inputSendAccumulator %= InputSendIntervalSeconds;

		var move = Input.AnalogMove.ClampLength( 1 );
		var buttons = PlayerInputButtons.None;
		if ( Input.Down( "run" ) ) buttons |= PlayerInputButtons.Run;
		if ( Input.Down( "duck" ) ) buttons |= PlayerInputButtons.Duck;
		if ( _pendingJump ) buttons |= PlayerInputButtons.Jump;
		var eye = _predictedController is not null && _predictedController.IsValid()
			? _predictedController.EyeAngles
			: _localEyeAngles;
		var frame = new PlayerInputFrame(
			BodyGeneration,
			++_nextInputSequence,
			move.x,
			move.y,
			Math.Clamp( eye.pitch, -90f, 90f ),
			PlayerInputAdmission.NormalizeYaw( eye.yaw ),
			buttons,
			_nextJumpSequence );
		SubmitInputFrame( frame );
		_pendingJump = false;
		if ( _predictedBody is not null && _predictedBody.IsValid() )
		{
			_predictionHistory.Enqueue( new PredictedFrameSnapshot( frame.Sequence, _predictedBody.WorldPosition ) );
			while ( _predictionHistory.Count > PredictionHistoryCapacity ) _predictionHistory.Dequeue();
		}
	}

	private void ReconcilePrediction()
	{
		var movement = ProcessedMovementState;
		if ( !HasProcessedInput || movement.BodyGeneration != BodyGeneration ||
			movement.ProcessedInputSequence == _lastReconciledSequence || !movement.IsFinite ||
			_predictedBody is null || !_predictedBody.IsValid() ||
			AuthoritativeBody is null || !AuthoritativeBody.IsValid() ) return;
		_lastReconciledSequence = movement.ProcessedInputSequence;
		var found = false;
		var acknowledgedPrediction = Vector3.Zero;
		while ( _predictionHistory.Count > 0 )
		{
			var sample = _predictionHistory.Peek();
			if ( sample.Sequence == movement.ProcessedInputSequence )
			{
				found = true;
				acknowledgedPrediction = sample.Position;
				_predictionHistory.Dequeue();
				break;
			}
			if ( PlayerInputAdmission.IsNewer( sample.Sequence, movement.ProcessedInputSequence ) ) break;
			_predictionHistory.Dequeue();
		}

		var current = _predictedBody.WorldPosition;
		var plan = PredictionReconciliation.Calculate(
			found,
			new PredictionPosition( movement.Position.x, movement.Position.y, movement.Position.z ),
			new PredictionPosition( acknowledgedPrediction.x, acknowledgedPrediction.y, acknowledgedPrediction.z ),
			new PredictionPosition( current.x, current.y, current.z ) );
		var correction = new Vector3( plan.Delta.X, plan.Delta.Y, plan.Delta.Z );
		switch ( plan.Kind )
		{
			case PredictionCorrectionKind.Hard:
				_predictedBody.WorldPosition = found
					? _predictedBody.WorldPosition + correction
					: movement.Position;
				_predictedBody.Transform.ClearInterpolation();
				_smoothCorrection = Vector3.Zero;
				break;
			case PredictionCorrectionKind.Smooth:
				_smoothCorrection = correction;
				break;
			case PredictionCorrectionKind.Ignore:
				_smoothCorrection = Vector3.Zero;
				break;
		}
	}

	private void ApplySmoothCorrection()
	{
		if ( _predictedBody is null || !_predictedBody.IsValid() || _smoothCorrection.IsNearlyZero() ) return;
		var fraction = Math.Clamp( Time.Delta / PredictionSmoothSeconds, 0f, 1f );
		var delta = _smoothCorrection * fraction;
		_predictedBody.WorldPosition += delta;
		_smoothCorrection -= delta;
	}

	private void UpdatePredictionPresentation()
	{
		var authority = AuthoritativeBody;
		if ( _presentedAuthoritativeBody is not null &&
			(!ReferenceEquals( _presentedAuthoritativeBody, authority ) || !IsOwnerLocal) )
		{
			if ( _presentedAuthoritativeBody.IsValid() ) _presentedAuthoritativeBody.Tags.Remove( "viewer" );
			_presentedAuthoritativeBody = null;
		}
		if ( !TryGetUsableAuthoritativeBody( out authority ) || !IsOwnerLocal ) return;
		_presentedAuthoritativeBody = authority;
		if ( _localThirdPerson ) authority.Tags.Remove( "viewer" );
		else authority.Tags.Add( "viewer" );
	}

	private void EnsureLocalGeneration()
	{
		if ( _localGeneration == BodyGeneration ) return;
		_localGeneration = BodyGeneration;
		_nextInputSequence = 0;
		_nextJumpSequence = 0;
		_pendingJump = false;
		_lastReconciledSequence = 0;
		_inputSendAccumulator = InputSendIntervalSeconds;
		_predictionHistory.Clear();
		_smoothCorrection = Vector3.Zero;
		_localLookInitialized = false;
		if ( AuthoritativeBody is { } body && body.IsValid() &&
			body.Components.Get<PlayerController>() is { } controller )
		{
			_localEyeAngles = controller.EyeAngles;
			_localLookInitialized = true;
		}
	}

	private void UpdateLocalPresentationInput()
	{
		if ( !TryGetUsableAuthoritativeBody( out var body ) ) return;
		if ( Sandbox.Networking.IsHost )
		{
			if ( !_localLookInitialized && body.Components.Get<PlayerController>() is { } controller )
			{
				_localEyeAngles = controller.EyeAngles;
				_localLookInitialized = true;
			}
			_localEyeAngles += Input.AnalogLook;
			_localEyeAngles.pitch = Math.Clamp( _localEyeAngles.pitch, -90f, 90f );
			_localEyeAngles.yaw = PlayerInputAdmission.NormalizeYaw( _localEyeAngles.yaw );
			_localEyeAngles.roll = 0;
		}
		else if ( _predictedController is not null && _predictedController.IsValid() )
		{
			_localEyeAngles = _predictedController.EyeAngles;
			_localLookInitialized = true;
		}
		if ( Input.Pressed( "view" ) )
		{
			_localThirdPerson = !_localThirdPerson;
			_cameraDistance = 20f;
		}
	}

	private void UpdateListenHostInteraction()
	{
		if ( !Input.Pressed( "use" ) || !TryGetUsableAuthoritativeBody( out var body ) ) return;
		var controller = body.Components.Get<PlayerController>();
		var origin = controller?.EyePosition ?? body.WorldPosition + Vector3.Up * 64f;
		var ray = new Ray( origin, _localEyeAngles.ToRotation().Forward );
		var trace = Scene.Trace.Ray( ray, 130f )
			.IgnoreGameObjectHierarchy( body.Root )
			.WithoutTags( "prediction" )
			.Run();
		if ( trace.GameObject is null ) return;
		foreach ( var pressable in trace.GameObject.GetComponentsInParent<IPressable>( includeSelf: true ) )
		{
			var pressEvent = new IPressable.Event { Ray = ray, Source = this };
			if ( !pressable.CanPress( pressEvent ) ) continue;
			_ = pressable.Press( pressEvent );
			break;
		}
	}

	int ICameraModifier.CameraOrder => 0;

	void ICameraModifier.ModifyCamera( CameraComponent camera, ref CameraView view )
	{
		if ( !IsOwnerLocal || Scene.Camera != camera || !TryGetUsableAuthoritativeBody( out var body ) ) return;
		var controller = body.Components.Get<PlayerController>();
		var eyePosition = controller?.EyePosition ?? body.WorldPosition + Vector3.Up * 64f;
		var rotation = _localEyeAngles.ToRotation();
		camera.RenderExcludeTags.Add( "viewer" );
		if ( _localThirdPerson )
		{
			var cameraDelta = rotation.Forward * -256f + rotation.Up * 12f;
			var cameraTrace = Scene.Trace.FromTo( eyePosition, eyePosition + cameraDelta )
				.IgnoreGameObjectHierarchy( body.Root )
				.WithoutTags( "prediction" )
				.Radius( 8 )
				.Run();
			if ( cameraTrace.StartedSolid )
				_cameraDistance = _cameraDistance.LerpTo( cameraDelta.Length, Time.Delta * 100f );
			else if ( cameraTrace.Distance < _cameraDistance )
				_cameraDistance = _cameraDistance.LerpTo( cameraTrace.Distance, Time.Delta * 200f );
			else
				_cameraDistance = _cameraDistance.LerpTo( cameraTrace.Distance, Time.Delta * 2f );
			eyePosition += cameraDelta.Normal * _cameraDistance;
		}
		view.Position = eyePosition;
		view.Rotation = rotation;
		view.FieldOfView = Preferences.FieldOfView;
	}

	private void DestroyOrRetain( GameObject? body, string reason )
	{
		if ( body is null || !body.IsValid() ) return;
		try { body.Destroy(); }
		catch ( Exception exception )
		{
			Log.Error( exception, $"Hexagon could not destroy a {reason} authoritative body; cleanup will retry." );
		}
		if ( body.IsValid() && !_retiredBodies.Contains( body ) ) _retiredBodies.Add( body );
	}

	private void CleanupRetiredBodies( bool publishRemoval = true )
	{
		for ( var index = _retiredBodies.Count - 1; index >= 0; index-- )
		{
			var body = _retiredBodies[index];
			var retry = AuthoritativeBodyRetirement.Retire(
				() => body.IsValid(),
				() =>
				{
					body.Enabled = false;
					if ( publishRemoval ) body.Network.Refresh();
				},
				body.Destroy,
				() => { } );
			foreach ( var failure in retry.Failures )
				Log.Error( failure, "Hexagon retired authoritative body cleanup retry was degraded." );
			if ( retry.IsClean ) _retiredBodies.RemoveAt( index );
		}
	}

	private OperationResult DestroyAuthoritativeBody( bool publishRemoval )
	{
		var previous = AuthoritativeBody;
		AuthoritativeBodyRetirementResult? retirement = null;
		if ( previous is not null )
		{
			retirement = AuthoritativeBodyRetirement.Retire(
				() => previous.IsValid(),
				() =>
				{
					previous.Enabled = false;
					if ( publishRemoval ) previous.Network.Refresh();
				},
				previous.Destroy,
				() =>
				{
					if ( !_retiredBodies.Contains( previous ) ) _retiredBodies.Add( previous );
				} );
			foreach ( var failure in retirement.Failures )
				Log.Error( failure, "Hexagon authoritative body strip cleanup was degraded." );
		}
		AuthoritativeBody = null;
		AdvanceBodyGeneration();
		ResetHostInput();
		Exception? publicationFailure = null;
		if ( publishRemoval )
		{
			try { GameObject.Network.Refresh(); }
			catch ( Exception exception ) { publicationFailure = exception; }
		}
		if ( retirement?.IsClean != false && publicationFailure is null ) return OperationResult.Success();
		if ( publicationFailure is not null )
			Log.Error( publicationFailure, "Hexagon could not publish authoritative body removal." );
		return OperationResult.Failure(
			ErrorCode.InternalError,
			retirement?.Retained == true
				? "The authoritative body was disabled and retained for cleanup retry."
				: "The authoritative body could not be fully destroyed." );
	}

	private void DestroyPredictedBody()
	{
		if ( _presentedAuthoritativeBody is not null && _presentedAuthoritativeBody.IsValid() )
			_presentedAuthoritativeBody.Tags.Remove( "viewer" );
		_presentedAuthoritativeBody = null;
		if ( _predictedBody is not null && _predictedBody.IsValid() ) _predictedBody.Destroy();
		_predictedBody = null;
		_predictedController = null;
		_predictedGeneration = -1;
		_predictionHistory.Clear();
		_smoothCorrection = Vector3.Zero;
	}

	private void AdvanceBodyGeneration()
	{
		BodyGeneration = BodyGeneration == int.MaxValue ? 1 : BodyGeneration + 1;
		HasProcessedInput = false;
		ProcessedInputSequence = 0;
		ProcessedMovementState = default;
	}

	private void ResetHostInput()
	{
		_inputAdmission.Reset();
		HasProcessedInput = false;
		_latestHostInput = default;
		_hasLatestHostInput = false;
		_lastAcceptedInputSeconds = 0;
		_lastAppliedJumpSequence = 0;
		_hasAppliedJumpSequence = false;
		_lastJumpSeconds = double.NegativeInfinity;
	}

	private static double MonotonicSeconds() =>
		Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;

	protected override void OnDestroy()
	{
		DestroyPredictedBody();
		HostInputAuthenticator = null;
		if ( Sandbox.Networking.IsHost )
		{
			_preparedBody?.Dispose();
			_preparedBody = null;
			_stripRequestedDuringPreparation = false;
			_ = DestroyAuthoritativeBody( publishRemoval: false );
			CleanupRetiredBodies( publishRemoval: false );
		}
		base.OnDestroy();
	}

	public sealed class PreparedAuthoritativeBody : IDisposable
	{
		private readonly HexPlayerBody _owner;
		private readonly GameObject _candidate;
		private readonly GameObject? _previous;
		private bool _completed;
		private bool _activated;
		private OperationError? _failure;

		internal PreparedAuthoritativeBody( HexPlayerBody owner, GameObject candidate, GameObject? previous )
		{
			_owner = owner;
			_candidate = candidate;
			_previous = previous;
		}

		public OperationResult<GameObject> TryActivate()
		{
			if ( _completed )
			{
				if ( _activated && ReferenceEquals( _owner.AuthoritativeBody, _candidate ) && _candidate.IsValid() )
					return OperationResult<GameObject>.Success( _candidate );
				return OperationResult<GameObject>.Failure(
					_failure?.Code ?? ErrorCode.Conflict,
					_failure?.Message ?? "The authoritative body replacement is no longer active.",
					_failure?.Details );
			}
			if ( _owner._stripRequestedDuringPreparation )
			{
				Abort( completeDeferredStrip: true );
				return OperationResult<GameObject>.Failure(
					ErrorCode.Conflict, "The authoritative body replacement was invalidated before activation." );
			}

			_completed = true;
			_owner._preparedBody = null;
			_owner._stripRequestedDuringPreparation = false;
			var previousGeneration = _owner.BodyGeneration;
			var previousHasProcessedInput = _owner.HasProcessedInput;
			var previousProcessedSequence = _owner.ProcessedInputSequence;
			var previousMovement = _owner.ProcessedMovementState;
			var activated = AuthoritativeBodyReplacement.RequireActivated(
				_candidate,
				activateCandidate: () =>
				{
					_candidate.Enabled = true;
					_candidate.Network.Refresh();
					return _candidate.IsValid() && _candidate.Enabled;
				},
				quiescePrevious: () =>
				{
					if ( _previous is null || !_previous.IsValid() ) return true;
					_previous.Enabled = false;
					_previous.Network.Refresh();
					return !_previous.Enabled;
				},
				publishCandidate: () =>
				{
					_owner.AuthoritativeBody = _candidate;
					_owner.AdvanceBodyGeneration();
					var controller = _candidate.Components.Get<PlayerController>();
					_owner.ProcessedMovementState = new PlayerMovementState(
						_owner.BodyGeneration,
						0,
						_candidate.WorldPosition,
						controller?.Velocity ?? Vector3.Zero,
						controller?.EyeAngles.pitch ?? 0,
						controller?.EyeAngles.yaw ?? 0,
						controller?.IsOnGround ?? false,
						controller?.IsDucking ?? false );
					_owner.GameObject.Network.Refresh();
					return ReferenceEquals( _owner.AuthoritativeBody, _candidate ) && _candidate.Enabled;
				},
				rollbackPublication: () =>
				{
					_owner.AuthoritativeBody = _previous;
					_owner.BodyGeneration = previousGeneration;
					_owner.HasProcessedInput = previousHasProcessedInput;
					_owner.ProcessedInputSequence = previousProcessedSequence;
					_owner.ProcessedMovementState = previousMovement;
					_owner.GameObject.Network.Refresh();
				},
				restorePrevious: () =>
				{
					if ( _previous is null || !_previous.IsValid() ) return;
					_previous.Enabled = true;
					_previous.Network.Refresh();
				},
				discardCandidate: () =>
				{
					try
					{
						if ( _candidate.IsValid() )
						{
							_candidate.Enabled = false;
							_candidate.Network.Refresh();
						}
					}
					catch ( Exception exception )
					{
						Log.Error( exception, "Hexagon could not disable a failed authoritative body candidate." );
					}
					_owner.DestroyOrRetain( _candidate, "failed replacement" );
				},
				retirePrevious: () => _owner.DestroyOrRetain( _previous, "retired" ) );
			if ( activated.Failed )
			{
				_failure = activated.Error;
				Log.Error( $"Hexagon could not atomically activate a prepared authoritative body: {_failure!.Message}" );
				return activated;
			}
			_activated = true;
			_owner.ResetHostInput();
			return activated;
		}

		public void Dispose()
		{
			if ( _completed ) return;
			Abort( _owner._stripRequestedDuringPreparation );
		}

		private void Abort( bool completeDeferredStrip )
		{
			_completed = true;
			if ( ReferenceEquals( _owner._preparedBody, this ) ) _owner._preparedBody = null;
			_owner._stripRequestedDuringPreparation = false;
			_failure = new OperationError(
				ErrorCode.Conflict, "The authoritative body replacement was aborted." );
			_owner.DestroyOrRetain( _candidate, "aborted replacement" );
			if ( completeDeferredStrip ) _ = _owner.DestroyAuthoritativeBody( !Game.IsClosing );
		}
	}

	private readonly record struct PredictedFrameSnapshot( uint Sequence, Vector3 Position );
}

public sealed class HexLocalPredictionMarker : Component
{
	private HexPlayerBody? _owner;
	private int _generation;

	internal void Bind( HexPlayerBody owner, int generation )
	{
		_owner = owner;
		_generation = generation;
	}

	internal bool IsCurrent( Component source ) =>
		_owner is not null && _owner.IsValid() &&
		_owner.GameObject.Network.Active && _owner.GameObject.Network.IsOwner &&
		_owner.BodyGeneration == _generation &&
		ReferenceEquals( source.GameObject, GameObject );
}