Odd GhostField/Rotation behavior

I’m experiencing odd behavior with my client predicted player controller. When I move the mouse/gamepad right stick to rotate my view, after I stop moving the view moves back a tiny bit the direction it came from. It’s hard to tell but this may be happening constantly the whole time I rotate the player’s view. But I can see it for sure happens when I stop rotating the view. This only happens when the render frame rate is lower than the prediction rate. When the render rate is higher, it feels totally smooth. I managed to minimally reproduce this issue with the following code. When I run this code the object behaves the same way I described the player view. I’ve also attached a screenshot of the prefab that is running this test code. Perhaps I missed some important step when setting up the various components, systems, and prefab. Any ideas?

Minimal Reproduction Code

using Unity.Collections;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.InputSystem;

public struct TestGhostInput : IInputComponentData
{
    public float yawSpeed;
    public int frame;
}

public struct TestGhostRotation : IComponentData
{
    [GhostField]
    public float yaw;
}

public struct TestGhostSpawner : IComponentData
{
    public Entity prefab;
}

[UpdateInGroup(typeof(GhostInputSystemGroup))]
public partial class TestGhostInputSystem : SystemBase
{
    int frame = 1;
   
    protected override void OnUpdate()
    {
        foreach (var input in SystemAPI.Query<RefRW<TestGhostInput>>().WithAll<GhostOwnerIsLocal>())
        {
            float2 inputValue = Gamepad.current.rightStick.ReadValue();
            input.ValueRW.yawSpeed = inputValue.x * 10;
            input.ValueRW.frame = frame;
            Debug.Log($"Input {frame} | speed {input.ValueRW.yawSpeed}");
        }
        frame++;
    }
}

[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
public partial struct TestGhostRotationSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (input, rotation) in SystemAPI.Query<TestGhostInput, RefRW<TestGhostRotation>>().WithAll<Simulate>())
        {
            rotation.ValueRW.yaw += input.yawSpeed * SystemAPI.Time.DeltaTime;
            string clientServerLog = state.World.IsServer() ? "Server" : "Client";
            Debug.Log($"Predict.{clientServerLog} {input.frame} | speed {input.yawSpeed} | yaw {rotation.ValueRW.yaw} | deltaTime {SystemAPI.Time.DeltaTime}");
        }
    }
}

[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
[UpdateAfter(typeof(TestGhostRotationSystem))]
public partial struct TestGhostTransformSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (rotation, transform) in SystemAPI.Query<TestGhostRotation, RefRW<LocalTransform>>().WithAll<Simulate>())
        {
            transform.ValueRW.Rotation = quaternion.EulerYXZ(0, rotation.yaw, 0);
        }
    }
}

[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial struct TestGhostSpawnerServerSystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<TestGhostSpawner>();
        state.RequireForUpdate<NetworkStreamInGame>();
    }
   
    public void OnUpdate(ref SystemState state)
    {
        TestGhostSpawner spawner = SystemAPI.GetSingleton<TestGhostSpawner>();
        EntityCommandBuffer ecb = new(Allocator.Temp);
        foreach (var networkId in SystemAPI.Query<NetworkId>())
        {
            Entity instance = ecb.Instantiate(spawner.prefab);
            ecb.SetComponent(instance, new GhostOwner() { NetworkId = networkId.Value });
        }
        ecb.Playback(state.EntityManager);
        state.Enabled = false;
    }
}

using Unity.Entities;
using UnityEngine;

public class TestGhostAuthoring : MonoBehaviour
{
    class Baking : Baker<TestGhostAuthoring>
    {
        public override void Bake(TestGhostAuthoring authoring)
        {
            Entity entity = GetEntity(TransformUsageFlags.Dynamic);
            AddComponent(entity, new TestGhostInput());
            AddComponent(entity, new TestGhostRotation());
        }
    }
}

using Unity.Entities;
using UnityEngine;

public class TestGhostSpawnerAuthoring : MonoBehaviour
{
    public GameObject Prefab;

    public class PositionerSpawnerBaker : Baker<TestGhostSpawnerAuthoring>
    {
        public override void Bake(TestGhostSpawnerAuthoring authoring)
        {
            var entity = GetEntity(TransformUsageFlags.Dynamic);
            AddComponent(entity, new TestGhostSpawner() { prefab = GetEntity(authoring.Prefab, TransformUsageFlags.Dynamic) });
        }
    }
}

After logging some data to the console every frame I can see that the server and client are getting out of sync. I bet the camera jittering is just the client getting corrected by new server values. I am running the client and server on the same machine in the Unity Editor on my PC. I’m using Application.targetFrameRate to set the frame rate to something low like 20 to induce this behavior. I notice that the server then appears to run at that rate too. At the same time I set the ClientServerTickRate.SimulationTickRate to 60. Why would the server be running at the lower frame rate in that case?
My ultimate goal is to have smooth first person player controls in a server authoritative game. Does any one have any ideas here?

Here is a screenshot of logs I printed out. You can see the first Input log shows some input then the rest of the input logs show 0. This is me letting go of the right stick. When I do that, the final client prediction log of each frame shows that it is correctly handling the latest input. But the corresponding server values for the same frame don’t end up the same as what the client predicted. They are less. So then the client reduces the value over a few frames to match the server. I, as the player, experience this as me pushing the right stick right to rotate the camera to the right, then I let go and the camera jerks left just a bit before being still. Hopefully someone has some experience with this and can help me here. Making a smooth first person camera is critical for me.

Hey joshrs926,

Thanks for the report, we will take a look. I think I remember occasionally seeing this too, in the editor. Can you file a bug report via the editor, for tracking purposes, please? Cheers.