Interactions on Target Entity and predictions

I am currently working on a interaction system for a networked game and I have come to hit a snag - mostly due to the lack of knowledge with Netcode for entities, so I am hoping someone can clear my misconceptions, or guide me in the right direction.

Current Setup:

Use OverlapSphere to find all entities that the user can Interact with, and generate a list - that is then shown on a ui, the ui can pick a interaction to execute from this list.

The user can then select a interaction to execute.

The way I made that work is by using IEnabledComponent that when its true, gets caught in a system for that specific interaction.

Client System: This systems are currently running in SimulationGroup

 if (physicsWorld.OverlapSphereCustom(intereactionCheckPosition, intereactionRadius, ref hitCollector, characterComponent.ValueRO.Interactables))
                {
                    foreach (var hit in hitCollector.AllHits)
                    {
                        //Get Interaction Component
                        if (!SystemAPI.HasComponent<InteractionComponent>(hit.Entity))
                            continue;
                        var interactionComponent = SystemAPI.GetComponent<InteractionComponent>(hit.Entity);

                        var position = l2wComponentLookup[hit.Entity].Position;
                 
                        interactions.Add(interactionComponent);

                        if (Input.GetKeyDown(KeyCode.F)) // This is to be changed by UI picker, since we can have a list of interactions
                        {
                            Debug.Log($"{interactionComponent.InteractionName}");
                            SystemAPI.SetComponentEnabled<InteractionComponent>(hit.Entity, true);
                        }
                    }
                }

Somewhere else there is another system:

            var ecb = new EntityCommandBuffer(Allocator.Temp);
            foreach (var (powerInteraction, interaction, entity) in SystemAPI.Query<RefRO<PowerInteraction>, RefRO<InteractionComponent>>()
                         .WithAll<InteractionComponent>().WithEntityAccess())
            {
                var rpc = state.EntityManager.CreateEntity();
                ecb.AddComponent(rpc, new SendRpcCommandRequest { });
                ecb.AddComponent(rpc, new SendPowerInteractionRequest
                {
                    Target = interaction.ValueRO.Entity // Entity this interactions applies to
                });
                ecb.SetComponentEnabled<InteractionComponent>(entity, false);
            }

            ecb.Playback(state.EntityManager);

And I have a Server System that listens to that RPC and executes the action. That works for simple actions, for example change GhostField value.

Problem

Problem is, how do I handle prediction on the client side? For example, lets say one interaction is to Kick a ball (rigidbody) around, I would like that the client does not need to wait for the round trip - so it would predict the interaction.

Is the idea, that the interaction system runs in the Predicted System Loop, and it applies the force on the client side, and the server will then hopefully do the same? This has the downside that Server Code and Client code would be slightly different, since the server uses RPC to trigger the action, and client will just straight up apply the force? - Is this even the case, or should the client not even apply the force at all?

How would I organize such a system?
There will be a large number of interactions - but they are low frequency, hence the RPC nature I tried.

The server does not need to use RPCs to denote any of these.

Just decouple the client input processing (you should poll the input in the InputSystemGroup and store that somewhere).
The actual action from a client perspective is to mark which entity / entities is controlling / interacting (you used an enableable component for that and then an RPC to inform the users he is controlling the entity).

You can specify who is the Owner for that entity by chaining the Owner component (i.e when the entity is requested by a client, if the request is not denied you assign the GhostOwner component to its network id).
That is immediately reflected on the client that see that it can control that entity (OwnerIsLocal will be set to true). There are certain downside of that though (time line shift more specifically).

You can also use you own component to denote who is the client eligible to control the entity, as well as use another component or buffer on the player itself (or the connection) that store the entities it is controlling.

The system must run in the prediction loop in this case, can may be slightly different, but there is no necessity of RPCs (even if these changes are low frequency). Everything is delta compressed (the replicated information) so bandwidth side it not a big of a deal.

There is always some chance of misprediction, especially if there are concurrent users trying to control that ball for example (first come wins). The easiest and actually probably best thing to do is to try to compensate a little the latency here and use some “animation”/“latency hiding” method to delay on the client the tick when the client start kicking the ball (i.e the action is delayed by 2/3 ticks). That give already the chance to receive the information for the server by the time the action trigger.
In all cases, just apply the force to the ball client side. Worst case scenario, the client could not do that, some misrprediction would occur and correction made.
The key there is then to smooth these misrprediction out (especially the visual one) and this can be achieved using a custom smoothing function

That was really insightful, I think I understand a bit more. The thing I am now debating is the assign a entity to a client.
So what you are saying, is that if I want to interact with something (lets say, a door, button, etc) I should make the client the owner, perform the action/update and then remove the owner back ?

That’s the bit I cant wrap my head around, usually you would send a RPC saying KickBall(target.entity). I guess the pattern is to always own the entity do what you want, and the release it back?