#lightyear
1 messages · Page 14 of 1
hm the new implementation is much simpler, do you want to try to upgrade?
i haven't release because aeronet hasn't been updated, but maybe I should ? UDP works
I keep crossing my toes every upgrade hoping the input lockup suddenly goes away 🤣
I can try it out
I'm still on 0.16
Is main on 0.16?
It's this branch; on 0.17: https://github.com/cBournhonesque/lightyear/pull/1229
I guess I should just merge it
ah. ok, I am not ready for 0.17
I'll circle back on it though
My project has a lot of dependencies that need to migrate to 0.17 first
SG!
btw, I think the input bug in 0.16 is somehow related to adding FrameInterpolate::<Transform> to an entity when it is being "moved" via input which changes the Position/LinearVelocity
In 0.16 I don't think it's correct to use FrameInterpolate::<Transform>, you should directly use FrameInterpolate::<Position> and FrameInterpolate::<Rotation>
In 0.17 I'm adding custom systems to do the conversion from Position/Rotation to Transform, so that you can replicate Position but do FrameInterpolation/Correction with Transform if you want to
Yes, that's exactly what I want to do.
My multiplayer game has physics, and as far as I understand, avian2d works best when you are manipulating the linearvelocity (as opposed to just teleport-moving objects +/- 20 pixels every input), but I need my transform to interpolate.
It's a bit hard to get to configure right because there's a lot of "gotchas" with this; need to disable the avian SyncPlugin, need to add FrameInterpolate::<Position> on the interpolated/predicts components, etc.
I agree, it's super hard to get it right
Oh, one more question I had for you, Periwink. I need my transform adjustments to propagate to children. Can you account for that with your new approach?
My entity for the player looks like this:
Player
- Transform
- Position
- LinearVelocity
- Children:
- (Avatar, Transform)
- (Nametag, Transform)
- (Shadow, Transform)
and those children have their own transforms relative to the player's position
Why not interpolate Position/Rotation? And then propagate from Position/Rotation to Transform after the interpolation
Not really familiar with that part - just re-enabling this right? If so, the result is the same on my end, still have the stuttering movement
are you sure you're using my branch? it's already enabled https://github.com/cBournhonesque/lightyear/pull/1229/files#diff-7b77ad2514a74ac81b64aa06c15f296a3e3d84f74049b3be2fda66f8d8f3d529
ah no I have been working off of master since the beginning - sorry for the confusion
I'll try it on your branch. I wasn't using it because I thought avian wasn't ready for .17 yet
No worries; it's not released but their main branch is on 0.17
i will also merge this branch to master today
oh nice, might just wait til then anyway
I don't think the approach handles child Transforms currently right now; propagation is a bit more involved. I would just stick to using Position directly in that case
I merged
@pine cape Seems like the example is bugged on master (see video)
Possibly related:
just pulled, still bugged though behavior is slightly different. video shortly
When characters fly off screen it's happening even though no keys are being pressed anymore. No apparent difference on the input side for why it sometimes is "stuck" vs starts moving
Sorry about that, will investigate later
I had tested this on my branch, but looks like i messed up something during the merge
no worries!
Sorry, not sure what you mean here.
Do you mean to interpolate the pos/rotation, and then add a system after InterpolationSet::VisualInterpolation which propagates/updates the transform and child transforms based on that?
--
Question 2: What does Rebroadcasting BEI inputs do?
Yes exactly, actually there already is a system is PostUpdate which updates Transforms from Position, so you just need to use FrameInterpolation<Position>.
The server will forward the inputs from client 1 to client 2. Client 2 can then use client 1's inputs to predict client 1's actions
Did you use main? I just tried on main and it works for me
yep, just confirmed. on main, up to date with origin. same issue
running it with three instances:
cargo run -- server
cargo run -- client -c 1
cargo run -- client -c 2
let me know if there's any other info that would help
I'm using
rm -rf SL && cargo run --no-default-features --features=server,netcode,udp -- server 2>&1 | tee SL
rm -rf CL1 && cargo run --no-default-features --features=client,netcode,gui,udp -- client -c 1 2>&1 | tee CL1
rm -rf CL2 && cargo run --no-default-features --features=client,netcode,gui,udp -- client -c 2 2>&1 | tee CL2
I just see an issue where a client has a hard time pushing another, but otherwise things seem fine
I seem to be running into something similar to #1189344685546811564 message on 0.24.2. Adding an seemingly unrelated observer causes the warn WARN aeronet_io::packet: 223v1 has 1 received packets which have not been consumed - this indicates a bug in code above the IO layer and causes no replication to happen.
This is fine:
// Observers
app.add_observer(handle_card_draw);
app.add_observer(add_card_visuals);
//app.add_observer(add_hand_visuals);
While this is not:
// Observers
app.add_observer(handle_card_draw);
app.add_observer(add_card_visuals);
app.add_observer(add_hand_visuals);
It doesn't seem like this observer needs to be related at all to replication:
#[derive(Event)]
pub struct Test;
pub fn add_hand_visuals(trigger: Trigger<Test>, mut commands: Commands) {
println!("Something");
}
Very very strange. Any thoughts @pine cape?
Hi, i'm trying to selectively replicate components as the ComponentRegistry has many component I have registered for luau scripting on the server side.
In the lightyear book, I found the mention that
By default, every component in the entity that is part of the ComponentRegistry will be replicated.
However I did not find anywhere in the book on how to diverge from this default behavior, what would be the best practice to selectively send specific components in Lightyear?
Thanks in advance !
Very strange. Probably related to how trigger events are serialized? Do you have a minimal repro? That would be super helpful
Sorry i am on my phone.
You can try looking at ReplicationConfig to disable a component by default. You should be able to update this when registering the component
Then to override the replication for a given entity you can look at ComponentReplicationOverrides
I'll try to get you a minimal repo, but I suspect that it probably crops up in very specific circumstances so it might be hard to try and reproduce minimally. At the very least I can get you a copy of my project where it's happening
Thank you very much for the info. I'll keep a log of what I do and might create a PR for the book.
@pine cape Mr Peri npcs who are only interpolated, how would you make them collidable with predicted entities?
Seems like bevy_web_keepalive is pulling Bevy 0.16 (on main)
and...... not adding the bevy seedling plugin also fixes it?? This is so cursed
I'm planning on swapping to a completely predicted model to avoid this problem, but the way I've handled this in the current code base is to literally copy the confirmed position value onto the interpolated instance every tick.
fn fix_interpolation_system(
mut interpolated_query: Query<(
&mut Position,
&mut Rotation,
&mut LinearVelocity,
&mut AngularVelocity,
&Interpolated,
)>,
confirmed_query: Query<(
&Position,
&Rotation,
&LinearVelocity,
&AngularVelocity,
), (Without<Interpolated>, With<Confirmed>)>,
) {
if confirmed_query.is_empty() {
return;
}
for (
mut position,
mut rotation,
mut linear_velocity,
mut angular_velocity,
&Interpolated { confirmed_entity },
) in interpolated_query.iter_mut() {
let Ok((
&confirmed_position,
&confirmed_rotation,
&confirmed_linear_velocity,
&confirmed_angular_velocity,
)) = confirmed_query.get(confirmed_entity) else {
warn!("Unable to get confirmed position");
continue;
};
*position = confirmed_position;
*rotation = confirmed_rotation;
*linear_velocity = confirmed_linear_velocity;
*angular_velocity = confirmed_angular_velocity;
}
}
You will almost certainly get desyncs when colliding, but it will be less frequently than not copying from confirmed. There's probably a better way to handle this, but like I said, I plan on ditching this approach for full prediction.
I think you have several options:
- handle hit detection on the client side -> efficient but vulnerable to cheating
- lag compensation
- predict the NPCs
I also want to add a way to switch a projectile between the interpolated and predicted timelines
yeah this is needed: https://github.com/Nul-led/bevy_web_keepalive/pull/22
For the ReplicationConfig i meant this: https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentRegistration.html#method.with_replication_config
API documentation for the Rust ComponentRegistration struct in crate lightyear.
I think i just have too many to predict e.e
I didn't have time to dig back into it yet. But go though the API and ask around if I have questions !
Anyone used both replicon and lightyear? Trying to weigh up which to use for my project. It seems that lightyear is more extensive (e.g. out of the box supporting prediction/interpolation), but are there any potential downsides compared to replicon that I should be aware of?
replicaon has probably better documentation. I think it's also better optimized, but i don't think the CPU cost matters much unless you have a big project
I wish my Bevy 0.17 upgrade had the same diff lol
were there any changes to webtransport in the 0.25 pre-release? my client is unable to connect to the server for some reason now
trace logs barely indicate anything problematic..
this is my client's output:
2025-10-09T09:42:56.531049Z DEBUG lightyear_netcode::client_plugin: Starting netcode connection process
2025-10-09T09:42:56.532418Z DEBUG lightyear_netcode::client: client state changing from Disconnected to SendingConnectionRequest
2025-10-09T09:42:56.537858Z INFO lightyear_netcode::client: client connecting to server 192.168.88.22:5000 [1/1]
2025-10-09T09:42:56.542724Z TRACE lightyear_connection::client: Triggering LinkStart because Connect was triggered
2025-10-09T09:42:56.547592Z TRACE lightyear_aeronet: SessionEndpoint added on AeronetLink 111v1. Adding Linking on Link entity 99v0
2025-10-09T09:42:56.554658Z TRACE lightyear_aeronet: Session added on AeronetLink 111v1. Adding Linked on Link entity 99v0
2025-10-09T09:42:56.559281Z TRACE lightyear_aeronet: LocalAddr added on AeronetLink 111v1. Adding on Link entity 99v0
2025-10-09T09:42:56.565643Z TRACE lightyear_aeronet: PeerAddr added on AeronetLink 111v1. Adding on Link entity 99v0
2025-10-09T09:42:56.570051Z TRACE lightyear_aeronet: Received 0 packets
2025-10-09T09:42:56.575162Z DEBUG lightyear_netcode::client: client sending connection request packet to server
2025-10-09T09:42:56.580543Z TRACE lightyear_aeronet: Sending 1 packet
2025-10-09T09:42:56.587312Z TRACE lightyear_aeronet: Received 0 packets
2025-10-09T09:42:56.593969Z TRACE lightyear_aeronet: Sending 0 packet
2025-10-09T09:42:56.599579Z TRACE lightyear_sync::timeline::input: Recomputing input delay on config update! Input delay ticks: 0. Config: InputDelayConfig { minimum_input_delay_ticks: 0, maximum_input_delay_before_prediction: 0, maximum_predicted_ticks: 100 }
2025-10-09T09:42:56.606203Z TRACE lightyear_aeronet: Received 0 packets
2025-10-09T09:42:56.610923Z TRACE lightyear_aeronet: Sending 0 packet
2025-10-09T09:42:56.613688Z TRACE lightyear_sync::timeline::input: Recomputing input delay on config update! Input delay ticks: 0. Config: InputDelayConfig { minimum_input_delay_ticks: 0, maximum_input_delay_before_prediction: 0, maximum_predicted_ticks: 100 }
// spams these messages repeatedly with eventual resends with the "Sending 1 packet" log entry again
server:
2025-10-09T09:42:56.751756Z TRACE lightyear_aeronet: Session added on AeronetLink 108v0. Adding Linked on Link entity 109v0
2025-10-09T09:42:56.758364Z TRACE lightyear_aeronet: SessionEndpoint added on AeronetLink 108v0. Adding Linking on Link entity 109v0
2025-10-09T09:42:56.764349Z TRACE lightyear_aeronet: PeerAddr added on AeronetLink 108v0. Adding on Link entity 109v0
2025-10-09T09:43:05.870959Z TRACE lightyear_aeronet: Disconnected (reason: "Disconnected by remote: (dropped)") triggered added on AeronetLink 108v0. Adding Unlinked on Link entity 109v0
2025-10-09T09:43:05.877259Z TRACE lightyear_connection::client: Adding Disconnected because the link got Unlinked (reason: "Disconnected by remote: (dropped)")
don't know what the problem is, will try to reproduce this with the webtransport examples in the lightyear repo
Found this when trying to run the launcher example:
2025-10-09T09:58:09.420863Z WARN aeronet_io::packet: 108v0 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer
2025-10-09T09:58:09.431380Z WARN aeronet_io::packet: 108v0 has 1 received packets which have not been consumed - this indicates a bug in code above the IO layer
2025-10-09T09:58:09.531708Z WARN aeronet_io::packet: 108v0 has 1 received packets which have not been consumed - this indicates a bug in code above the IO layer
2025-10-09T09:58:09.631489Z WARN aeronet_io::packet: 108v0 has 1 received packets which have not been consumed - this indicates a bug in code above the IO layer
2025-10-09T09:58:09.732775Z WARN aeronet_io::packet: 108v0 has 1 received packets which have not been consumed - this indicates a bug in code above the IO layer
after enabling aeronet_io logs in my app, I got the same messages. So seems like a bug on the lightyear end?..
or maybe aeronet itself
Yeah aeronet is currently bugged, the author hasn't had the time to complete the upgrade
Only UDP works
I think I found an issue on the lightyear end:
https://github.com/cBournhonesque/lightyear/pull/1243
basically, an ordering issue of observers
webtransport works with this fix
Another issue that I noticed is that previously spawned entities don't get replicated to new clients. Haven't reproduced that on lightyear examples yet, so not sure where exactly the root issue is
well, I can neither reproduce this in lightyear, nor I understand what could be causing this issue in my app after updating to 0.25
commands.spawn((
TestEnemy,
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::Single(player_id.client_id)),
InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(player_id.client_id)),
ControlledBy {
owner: client_entity,
lifetime: Default::default(),
},
));
just tested this on a single marker component TestEnemy, and in my app the spawned entity just doesn't appear for clients that spawn later
Thanks, will check. So this is spawned on server before clients spawn?
Actually I have a test for it already https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests%2Fsrc%2Fclient_server%2Freplication.rs#L77
How do you query for the entity on the client? Do you use Controlled? Maybe that's the issue
I query only for the TestEnemy component, without any additional filters
yes
haven't run the test, but I couldn't reproduce the issue with the simple box example for some reason
I believe you still have the access to my mine_crawler repo btw. 😁 If you want, I can help you navigate through it (and give the instructions to run it) to reproduce the issue
I have reduced the repo as much as I can: https://github.com/IRSMsoso/minimal-consumption-bug. I also have my full project at the point the issue happened in another branch full-project.
Removing anything else causes the warn to stop printing (and presumably replication to work again but I don't know for sure since I removed spawning anything and was just focused on keeping the warn). For example, removing the adding of the CardEffectsPlugin (which just adds a bunch of global observers) causes the warn to stop.
I'm using the aeronet_steam peer to peer through lobby invites. I could try a different aeronet setup but it would take me a while to understand how. Let me know if you need anything else. I'm happy to help try and figure it out, but my knowledge of the lightyear codebase is pretty minimal.
This might be a novice question, but I couldnt find many examples of doing server authoritative movement while using client authoritative camera rotation in first person 3d. This is made more complicated since I'm sending my inputs through the leafwing integration.
There were no examples for controlling a player character in 3d on the lightyear page so I've had difficulties figuring this out.
i would recommend using bei
there is also the fps example
The fps example is in 2d
And its movement is relative to north/east, not relative to heading
Wym bei?
Bevy enhanced input
There's the 3d example: https://github.com/cBournhonesque/lightyear/tree/main/examples/avian_3d_character and the fps example https://github.com/cBournhonesque/lightyear/tree/main/examples/fps which follows the user's mouse
Oh thanks I get it now. I didnt make the connection with the bullets following the mouse
I get
src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp (2535) : Assertion Failed: Extra control data returned besides TOS? 0x0/0x1b
src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp (2535) : Assertion Failed: Extra control data returned besides TOS? 0x0/0x1b
src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp (2541) : No control data returned even though we asked for TOS?
src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp (2541) : No control data returned even though we asked for TOS?
on mac; i will have to try without steam
The problem with the avian one was that, since im doing a 3rd person orbit cam, I wanted specifically client authoritative camera object. The FPS example should help though
yeah, seems unrelated? https://github.com/ValveSoftware/GameNetworkingSockets/issues/359
I just realized something subtle, my script library uses register_type and not register_component, so the conflict I originally anticipated is absent.
@pine cape Sorry to bother mr peri but is there an event that get returned whenever a client fails to connect to server?
Something like I failed because x happened and so on
ah just noticed disconnected has reason
If you end up using BEI, the feature you're looking for is ActionMock, We use it to set the camera as a fake Vec3 input, so it's buffered for other players, but client authoritative.
Action:
#[derive(InputAction, Debug)]
#[action_output(Vec3)]
pub struct CameraForwardAction;
Components:
#[derive(Component, Default, Debug, Reflect, Serialize, Deserialize, Clone, PartialEq)]
#[reflect(Component)]
pub struct NetworkInputComponent;
#[derive(Component, Default, Debug, Reflect)]
#[reflect(Component)]
pub struct LocalNetworkedActionComponent;
Spawning the action:
context.spawn((
Action::<CameraForwardAction>::new(),
LocalNetworkedActionComponent, // needed to target since remote players will have
// no bindings since this is mocked
// Added to help lightyear pick up this action since it doesn't have bindings
InputMarker::<NetworkInputComponent>::default(),
));
Setting the value on the client:
fn third_person_camera_input_system(
transform: Single<&Transform, With<ThirdPersonCameraComponent>>,
action: Single<Entity, (With<Action<CameraForwardAction>>, With<LocalNetworkedActionComponent>)>,
mut commands: Commands,
) {
commands.entity(*action).insert(ActionMock::new(ActionState::Fired, transform.forward().as_vec3(), MockSpan::Manual));
}
With Leafwing the key is action_state.set_axis_pair() which you set the value from your camera. You can also do set_axis_triple() if you want a full Vec3 like we did.
Here's it in action in the FPS example:
https://github.com/cBournhonesque/lightyear/blob/eff683e615db5706a0cb01afa83ac83da9d53547/examples/fps/src/client.rs#L47
I did something similar I think. For BEI I have a local context (evaluated in PreUpdate) with a rotate action to update local camera for responsiveness and sets a mock actions values to the resulting yaw/pitch/roll in a separate networked context added with lightyear (evaluated in FixedPreUpdate I believe). It seems to work as desired
Oh thats super clever. So you treat the data as if it was an input, your server doesnt care since it doesnt need to know the details. Thanks
@pine cape Hello again, I came here to annoy you once again. So as I was building my world previously to actually starting my server. I noticed a few warning telling me no server was found. I am guessing a little bit of the logic utilizes of the server component.
Which in turn keeps spawming this log https://github.com/cBournhonesque/lightyear/blob/66c5d0f1d3fe6a8b1a75b7f768df1d190865bd68/lightyear_replication/src/send/components.rs#L398
The trigger for this error seems to be whenever I add the replicate component unto an entity. And server is not properly started, the hooks gets triggered therefore running this method. Not sure if I should worry, as everything works normally
The client log also is hella annoying
https://github.com/cBournhonesque/lightyear/blob/66c5d0f1d3fe6a8b1a75b7f768df1d190865bd68/lightyear_replication/src/send/components.rs#L380 - I guess he becomes very spawny because for a few ticks we do not have the replication sender
Do you mind if I make them a little less noisy?
Maybe you can set them to warn and then add lightyear_replication::send::components=error to your log filter?
I could do you mean set them on lightyear or my own branch:
You can set the warning on lightyear; thanks
https://github.com/cBournhonesque/lightyear/pull/1246 - @pine cape vive la france
Ty
I fixed most of the issues in the projectiles example, that's probably the most comprehensive example, that combines elements from rooms, avian, BEI, all possible replication modes, prespawning, etc.
Next thing i'm trying to understand/fix is why on the avian-3d example, the remote player's movement is not entirely smooth, even though the player presses the same input (so it should be perfectly predicted). For the local player (controlling the entity) everything is smooth, so FrameInterpolation is working properly.
But for the remote entity it's not smooth, even though (from looking at logs), the FrameInterpolation should be working correctly
It looks like one player (player controlling the entity, where things are smooth) has a higher framerate than the other; this could be a hint.
Hm it looks like a non-focused window will have its frame rate reduced, which might be why the non-focused window has less-smooth movement
Aha that was it! Alright the avian_3d example is pretty smooth now
Is BEI input replication to other clients working? I tried the projectiles example with inputs only and the other clients were getting errors received input message for unrecognized entity
Those errors should only appear at the beginning as things are still being replicated. If you can see the bot firing then the input replication is working
Is there a way to make it so that one replication group always gets replicated before another? I tried with just setting ones priority higher but it was not guaranteed.
What I'm trying to do is make a replicated hierarchy that replicates top to bottom in order. I've tried just making them all the same replication group but I get network lag when I do it like that.
I don't think there's a way currently. I should add a way for a received replication message to remain buffered on the receiver until any parent is received
How big is the hierarchy? I'd be interested in knowing how many network packets the message is split into for it to cause lags
The hierarchy looks like this:
Galaxy Root -> 1 Star -> 1 Planet -> 4 Moons -> (8 player controlled entities, 30~ interpolated missiles, 100~ deterministic bullets)
All of that in a single ReplicationGroup produces visible lag, but splitting each type into its own ReplicationGroup runs smoothly
I think for now I'll add NetworkVisibility to all entities and replicate child entities after a delay.
Sg!
I think with the avian release tomorrow all my deps will have upgraded, so I will probably do the release tomorrow evening as well
Might be more of a rust question than a lightyear question, but do you think it’s faster to have one ProjectileMarker component with a projectiletype enum, or each projectile type have its own component while using lightyear?
For example when handling collisions a system only handling one type of projectile has to loop through all the projectiles and check if it is the right type, kinda makes me thing separate components is the way to go, but then I’d have ~50 components to replicate.
How often do you need to iterate through only one type of projectile? That shouldn't happen too often no?
funny thing: the test works in the lightyear repo, but fails if I copypaste it as is to my project
seems like it breaks with a non-default feature selection
2025-10-13T13:38:17.962236Z INFO mc_server_lib::tests: Frame step client_tick=Some(Tick(99)) server_tick=Tick(99)
2025-10-13T13:38:17.972616Z INFO mc_server_lib::tests: Frame step client_tick=Some(Tick(100)) server_tick=Tick(100)
2025-10-13T13:38:17.972856Z ERROR lightyear_replication::send::components: ClientOf 117v0 not found or does not have ReplicationSender
2025-10-13T13:38:17.983139Z INFO mc_server_lib::tests: Frame step client_tick=Some(Tick(101)) server_tick=Tick(101)
2025-10-13T13:38:17.993914Z INFO mc_server_lib::tests: Frame step client_tick=Some(Tick(102)) server_tick=Tick(102)
test tests::test_spawn_new_connection ... FAILED
What features are you using?
trying to do default-features = false everywhere, unless it doesn't compile
narrowing it down atm, which one is missing...
Hi. I need some help in getting prediction to work. I have done the setup in Protocol and server and have a movement prediction function but the entity is getting spawned without the Predicted component.
It has ShouldBePredicted though:
you're missing a PredictionManager component on your Client entity
Ahh thank you
hm, that's probably not it.. mass-replacing default-features to true across the whole project didn't help
ok, not sure what was wrong with the tests, but I finally found the issue: I re-inserted the ReplicationSender component on the Connected observer trigger too (which was previously added by the LinkOf observer)
so that was wrong. after removing that insertion, replication of previously spawned entities works again
Yes adding it on LinkOf is more correct
If you add it on Connected, you will get issues with observers ordering (the replication-internal orderings also react to Connected to decide what to replicate, but only if the Link has ReplicationSender)
I sometimes get this warning on the client: Is this something to worry about?
2025-10-13T15:21:46.219714Z WARN calloop::loop_logic: [calloop] Received an event for non-existence source: TokenInner { id: 3, version: 4, sub_id: 0 }
Is it possible to disable input sync per entity? I want to have some entities with leafwing input actions excluded from input synchronisation, because they don't exist on the server, yet clients attempt to broadcast input updates anyway and my server spams with "Failed to map entity" warning
(btw, I think I didn't see those warning before, until I updated to main)
It happens pretty often actually, is there a lot of overhead to having many different components replicated?
It happens often because I’m actually talking about player abilities not just projectiles, so there is a lot of variation in how they play
There is no runtime overhead, but your compile time might be larger
Input sync only happens for entities that have an InputMarker component, which is a required component of leafwing's InputMap
I've never seen that but i would just ignore it
Released the new version: #crates message
Hey I'm trying to update to 0.17, and I'm switching to https://github.com/adrien-bon/bevy_ecs_tiled/blob/dev/examples/physics_avian_controller.rs instead of LDtK, I've come across a problem where the physics colliders are in the wrong position. When I remove
app.add_plugins(lightyear::avian2d::plugin::LightyearAvianPlugin {
replication_mode: AvianReplicationMode::Position,
..default()
});
They are in the correct position. I've tried adding a component to them which stores their transform when created and resetting their positions to those values later but that doesn't seem to work.
never mind I think I figured something out
@pine cape Would you like for me to reopen the lag compensation pr?
I just saw your comment
Yes please
@pine capeDoneeee~ now I have a teeny demand… follow me on GitHub, pwease~ hahahaha
So the replication_mode: AvianReplicationMode::PositionButInterpolateTransform seems to be effecting everything I spawn, they are all "locked" to Vec2(0,0) unless I repeat apply position to a different value, this includes stuff from predicted players, interpolated enemies, and non-networked positions like world colliders. But once they are spawned in they can move around fine
Could you please open an issue? Also I think TransformPropagation to children might not work properly
yeah sure!
So i tried only use AvianReplicationMode on the client and things seem to be working well
Which mode?
AvianReplicationMode::PositionButInterpolateTransform on just the client
and then no lightyearavianplugin on the server
I have a similar issue with entity positions being set to Vec::ZERO. Adding the following before adding PhysicsPlugins fixed it:
app.insert_resource(PhysicsTransformConfig {
propagate_before_physics: true,
position_to_transform: true,
transform_to_position: false,
transform_to_collider_scale: false,
});
Is it possible to avoid despawning replicated entities on the client when it disconnects from the server?
ControlledBy lifetime seems to be affecting only the server
We can add a setting for it, could you open an issue?
sure. I can even send a PR, kinda want it fixed rn, so a hint on where it happens would be appreciated 😁
btw, do you imagine it as a setting? or maybe it could be a marker component that clients would add to entities for which they'd want this behaviour disabled?
seems like it's in ReplicationReceivePlugin::handle_disconnection
Yep it's there, I think a Lifetime component, or maybe Persistent marker component.
If Persistent is present, we don't despawn the Replicated entity
Or maybe we can add the Persistent component directly to the ReplicationReceiver...
so
- if
Persistentis on ReplicationReceiver, we don't despawn any of the received entities - if it's not, then we despawn any Replicated entities that don't have the
Persistentmarker component
I'm not a fan that it kinda has the same purpose as ControlledBy::lifetime, while one affects only clients, and another - only servers. Maybe it would make sense to merge them into a single separate component in the future
and I can already foresee the confusion that this marker isn't replicated and should be added manually both on client and server - some users might not expect that
as I was wondering whether ControlledBy::lifetime affects client behaviour or not
Maybe, one is on the receiver side, the other on sender side. I'll probably rename them later
Ah I see what you're suggesting. That the sender specifies the behaviour for the receiver
well, that wasn't a suggestion per se, but an (invalid) assumption that that's how it already works :D
but I actually think that clients should be responsible for the choice
console spam on the server
starts as soon as client connects
what could be causing this?
i recreated the avian character example with BEI
and this
when i connect multiple clients this happens
the clients only see their own player moving
the other one just desyncs
i think i messed something up a lot
Maybe you can compare with the BEI example
i did, my BEI code is mostly based on that
i added the inspector and the Client entities are there so i dont know why it says that
same with both player entities
The BEI example uses both prediction and interpolation while the Avian character one only does prediction
Just updated to 0.25 and I'm getting an error when implementing Actionlike's input_control_kind function.
"CharacterAction does not implement 'FromReflect' so cannot be created through reflection."
Here is what I'm deriving for my CharacterAction enum, same as in 0.24 and as in the examples provided for 0.25
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Reflect, Serialize, Deserialize)]
haven't had any issues. Maybe ask in the leafwing channel?
Hello again:) The new update is looking solid! actually got frame interpolation working for the first time! I have one question of replicating Position, Here's what is happening:
Server spawns enemy, enemy is in the correct position,
Client joins and spawns the same enemy, enemy is at Vec::ZERO,
I manually change the position on the server slightly, and now the enemies are in the correct position on both client and server.
Do Positions not send updates if they have not changed?
My second question with Position is do you think we could have a server based ChangePositionWithoutFrameInterpolation component, message, event, something? It would be useful for A) reloading levels/changing levels B) any form of blink ability.
I'm going to need one, but It seems like everyone with frame interpolation would need one so maybe it should be apart of lightyear?
For your first question, is this for your interpolated entities?
This actually a subtle footgun, this is what's happening:
- for Predicted entities, your Position is replicated as Confirmed<Position>. This triggers an immediate rollback on the client which inserts the correct Position. If you're not getting an immediate rollback then it's a bug
- for Interpolated entities, it is possible that only one of Position or Rotation gets added (and not both at the same time). This can happen if
Rotationdoesn't get updated frequently for your entity. This can cause issues because thesync_pos_to_transformsystem from avian only does the sync from Pos/Rot -> Transform when BOTH are present on the same time. So you might be stuck with aTransform::default()for a short-while, until both Position/Rotation are present on the entity. For that reason it's best to add rendering components on interpolated entities only when BOTHPositionandRotationare present. - Lastly there is another subtle thing. Inserting
RigidBodyon an entity automatically insertsPosition/Rotation/Transformon it. For that reason you do NOT want to add RigidBody on interpolated entities because it's going to display the entity atTransform::default()until the first interpolation updates are received
These footguns are a bit unfortunate, but I think the change of merging Predicted/Interpolated/Confirmed into one entity is still the way to go, it makes a lot of things easier
I think your second point is valid, i'll open an issue. We want to avoid interpolating in those cases. Maybe i'll just add a PauseFrameInterpolation marker component that users can temporary add, what do you think?
I can also add a setting on FrameInterpolation to not interpolate if the component changed by more than X amount
Ah I was just being dumb, using workspaces and hadn't updated leafwing properly - sorted now 🙂
Added some pointers here: https://github.com/cBournhonesque/lightyear/pull/1267/files
I am actually disabling rotation syncing to save on networking on my interpolated enemies, I'll try enabling it! Thank you!!!! And I think merging interpolated/predicted/confirmed entities was also a great step, a lot less footguns:). And I would perfer the PauseFramInterpolation option, but up to you!
@pine cape The upgrades I did in my lag compensation pr are. They were in the old pr description, sorry to answer you here is just more convenient
- Multiple colliders can be lag compensated at once (I dont recall exactly) but I believe that was not possible before hand. I used a relationship for that
- We dont need to care about RC in avian as we do a slight prediction if we were to hit the aabb or not if so we spawn the collider at that given place in time (packet loss would not be accounted for AND I dont know how I would do so)
- That means dynamic bodies like BF4 bullets with trajectories and so on can still be accounted
- It also means it is technically more exact
Rotation disabling or adding rigidbodys client side after position seemed to fix it! Thanks again:)
I can try a PR for this if you think it would be simple, I typically don't code the best so maybe I'll leave it to you lol
Hm.. Now I also want to disable despawning of other disconnected players, to let clients despawn the entities themselves, after cleaning things up correctly
my use-case is a bit weird. I do want replication of despawn events, but I just want to avoid the last despawn by lightyear
Sure please have a go at it, I think it will be simple, especially the Pause version. Make sure that if we Pause we clear the cached FrameInterpolation values
I didn't understand. You want non-controlled replicated entities to not be despawned even if the server sends a despawn?
Or only in the case where remote clients are disconnected?
Yes and yes
I want to ignore the despawn only if it was caused by the disconnect
I know I can use Lifetime::Persistent on the server end, to keep the entity for longer. But it doesn't quite work for me, as I still want to despawn an entity on the server immediatelly, but later on the client end
Im a little confused as to where the smoothing is happening, I thought this would work but blinks still smoothly slide across the world:
#[derive(Component, PartialEq, Serialize, Deserialize, Clone, Debug, Reflect)]
pub struct SkipFrameInterpolation;
/// Currently we will only support components that are present in the protocol and have a SyncMetadata implementation
pub(crate) fn visual_interpolation<C: Component<Mutability = Mutable> + Clone + Debug>(
time: Res<Time<Fixed>>,
registry: Res<InterpolationRegistry>,
timeline: Single<&LocalTimeline, With<Client>>,
mut query: Query<(&mut C, &mut FrameInterpolate<C>, Option<&SkipFrameInterpolation>)>,
) {
let kind = DebugName::type_name::<C>();
let tick = timeline.now.tick;
// TODO: how should we get the overstep? the LocalTimeline is only incremented during FixedUpdate so has an overstep of 0.0
// the InputTimeline seems to have an overstep, but it doesn't match the Time<Fixed> overstep
let overstep = time.overstep_fraction();
for (mut component, mut interpolate_status, skip_interpolation) in query.iter_mut() {
if skip_interpolation.is_some() && interpolate_status.current_value.is_some() {
*component = interpolate_status.current_value.clone().unwrap();
interpolate_status.previous_value = interpolate_status.current_value.clone();
continue;
}
Ok I got it working, I'll send a PR request once I clean some stuff up!
You might be able to do this yourself.
Add a system that listens to a client disconnecting, then go through all ControlledByRemote entities and remove the Replicating component.
The entity will be despawned on the server, but the despawn won't be replicated to clients
@pine cape Great simplifcation making the confirmed entity as the predicted/interpolated one
unfortunately that caused me 600 problems, but that is fine.
it's an investment
Sorry to bother just a migration question, now with the change on RemoteTrigger, I need to use MessageSender and observer that event on server side?
RemoteTrigger didn't change much; it's just that the entity_event is now part of the trigger itself
Wait does the struct still exist? I thought it got deleted
So I have a question about predicted replication on controlled entities. Right now I have abilities like dashes, rolls, etc, that change the players' movement speed. I tried having movement speed in a replicated component, but that leads to rubber banding when the speed changes from a low value to a high value or vis versa. I think I have it fixed by having the client send a movement speed value as an input, so the server uses the exact movement speed used on the client for that specific AWSD movement tick. Does this make sense? Is there a better way to fix this issue? Like a general tick buffered component for stuff like movement speed?
I've just tried that, but it seems that it breaks replication of all other newly spawned entities
So I had the following scenario:
- Client
Adisconnected, server removedReplicatingcomponent for entityAand despawned it - Client
Bgot the event that clientAdisconnected, and removed entityAon its own - Client
Areconnects, gets its entity spawned by server, it got replicated to clietnAbut not for clientB - Client
Bdied and got its entity despawned by replication (which got replicated to every client) - Client
Bsent a message to restart - Server spawned a new entity for client
B, it appeared only for clientA, but not forB
so basically every client, that was connected while some entity got Replicating component removed on the server end, gets bugged
I think I found the fix:
https://github.com/cBournhonesque/lightyear/pull/1270
@pine cape is prediction no longer possible for immutable components? Seems like the immutable method variants have been removed, and add_prediction still requires SyncComponent (which in turn requires a mutable component)
But bevy now uses write_message, add_message and so on hehe
messages are buffered, events are triggered
I think the server's speed should be authoritative, and the client should predict it. You can use correction to smooth out the correction in the case of misprediction/rollback
yeah the variant methods for immutable also didn't allow for PredictionMode::Full, so it was just about making sure that they would be synced from the Confirmed to Predicted entity
but now that Confirmed and Predicted entities are merged, there's no need for that. The immutable component would be added directly on the Predicted entity
aha, I see, thanks!
If you want the speed to be client authoritative and tick-buffered, you would provide it as an Input
Does that mean relationships will be correctly replicated now?
Meaning predicted/confirmed entity will follow the same relationship structure built in server when it comes to ordering and so on
Yeah I’d prefer it to be server authoritative, what do you mean by use correction?
Call add_correction in your protocol, you can search for it in the examples
Hm I don't think so. I think we need an explicit step to order the relationship children to be replicated in the same order as in the parent
Interesting with avian refactor on forces there is no longer a need to keep them synced very handy
@pine cape hi again. Thx for the merge! Btw, I've just noticed that I'm still receiving "Replicated Entity ... not found in entity_query" messages. And in that PR I've changed them from trace level to warn
It seemed to me that non-existent replicated entity shouldn't be contained in the replicated_entities list, but apparently there are still cases where that can happen. Maybe you'll want to change the log level back or look into why entities still remain in that list (in case that's not working as designed)
you get this after pausing replication by removing Replicating?
@pine cape Do you know of any bugs resetting collider positions to 0?
hmm it seens adding a rigidbody static component resets position? I guess is this new onadd system
Yes check the docstring at the start of lightyear_avian/plugin.rs
Inserting RigidBody adds Position::default
I assume you get this on your Interpolated entities?
This is on the server side I believe is just a a bug in avian itself
Or even scaled up to bevy
Seems like it, yeah
I can't understand where entity_query of the replicate system gets its filter, but apparently we should also change With<Replicating> to Has<Replicating> and skip silently if it's got false
upd. oh, it's ReplicationSendPlugin, found it
I noticed it triggers a hooke 0.4.1 is gonna have a janky fix tho so I dont think that should be a worry
@pine cape Could you point out how componentreplicationoverrides work? Do I insert it unto the entitiy that is the replication sender, or the entity that I want to control it is replication type? My case scenario is, i want to use transform as position manager for all entities except my player entity (in this case he should use posittion/rotation)
ah I figured it out
Add unto the entity pointing out the replication sender that controls that enttiy
I am having the same issue with props spawned by the server. How can we handle this case instead?
Are the props predicted/interpolated?
Neither. Strangely, it worked before I upgraded to the latest version of Lightyear. I added prediction to it and it works, but is this the best thing to do for an entity that will never move? (Apart from despawning once broken, for example)
Also, before using lightyear, each prop had a RigidBody::Static and two child entities for colliders.
- One for player collision
- One with a
Sensor—usually larger than player collision—for breaking/opening interactions.
Now, the children no longer follow the parent entity when it moves.
I thought about making them separate entities (without adding ChildOf(PropEntity)) and using Bevy's custom Relationships with a system to follow the parent, but perhaps there's a way to setup lightyear to replicate the desired behavior?
I think it might be a bug in avian, I've think seen people mentioning it
Feature suggestion: Component-specific interpolation delay
Currently, all components share the same interpolation delay. Adding per-component delay settings would let high-frequency components interpolate with minimal latency, while letting low-frequency components use a longer delay to smooth over sparse updates without introducing unnecessary latency to other components.
This would likely pair well with component-specific replication frequency settings. For example, in my setup, I only update position velocity components once per second while coasting, and every tick (5 TPS) when actively moving. This behaviour may already be possible by splitting an entity's data into multiple ReplicationGroups with their own send_frequencies.
It should be possible, but how would the component-specific delay be communicated? via a one-time message from the server to client?
And yes component-specific replication frequency might be possible as well.
I think those are more niche improvements though, since htey are mostly performance focused
I'm a little confused (and possibly disappointed?) that from what I can tell there doesn't appear to be a way to use lightyear to make a connection without prior communication of a ConnectToken. With WebTransport, it appears to be possible (and secure?) to connect from a browser to an arbitrary server, given an address and certificate hash. I understand UDP on its own is not secure, but I believe the same is not true for WebTransport. So why?
Am I right in the assumption that there is "Netcode" and "Steam" based clients and that's it?
According to issue #138 there used to be Authentication::RequestConnectToken. What happened to that?
Okay, managed to find the commit that removed it and looks like it's just None except worded unfortunately? As in, you still require a separate "backend" / connection.
I want to support Clients that don't use Netcode, I just didn't get around to it
Basically the IO layer currently adds Linked/Unlinked, and the connection layer (Steam/Netcode) currently adds Connected/Disconnected.
IO = transmitting bytes
Connection = long-running connection over the IO with a unique PeerId
We should let IOs act as a Connection, so we would have 3 connection layers: Steam / Netcode / Raw
I've been trying to dig at this from a couple angles (stripping down netcode package, starting from the steam package) but I probably just don't have the know-how required. Is there anything I could do to make this happen?
hi, i just started messing with lightyear 0.25 and wondered if there was any physics based first person controller best practices examples
maybe the avian_3d example?
thats what im using as a base, i figured it out, it was a misunderstanding of how prediction works 🤡
I'm trying to setup webtransport, but it's stuck on client connecting to server 127.0.0.1:8384 [1/1], does anyone know how to fix it?
Client spawned with:
commands.spawn((
Client::default(),
LocalAddr(client_addr),
PeerAddr(server_addr),
Link::new(None),
ReplicationReceiver::default(),
NetcodeClient::new(auth, NetcodeConfig::default())?,
WebTransportClientIo {
certificate_digest: String::new(),
}
))
On web or on local?
Do you have a code snippet?
on local machine
Maybe try to see the difference between your setup and this: https://github.com/cBournhonesque/lightyear/tree/main/examples/simple_setup
My setup is mostly the same, I dont see where the issue could be.
Full client code:
use std::time::Duration;
use bevy::prelude::*;
use lightyear::{netcode::Key, prelude::{client::*, *}};
use reclipsis_shared::{FIXED_TIMESTEP_HZ, SharedPlugin};
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
ClientPlugins {
tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ),
},
SharedPlugin,
))
.add_systems(Startup, test_connect)
.run();
}
fn test_connect(mut commands: Commands) -> Result {
let server_addr = "127.0.0.1:8384".parse()?;
let client_addr = "127.0.0.1:8385".parse()?;
let auth = Authentication::Manual {
server_addr,
client_id: 0,
private_key: Key::default(),
protocol_id: 0,
};
let client = commands
.spawn((
Client::default(),
LocalAddr(client_addr),
PeerAddr(server_addr),
Link::new(None),
ReplicationReceiver::default(),
NetcodeClient::new(auth, NetcodeConfig::default())?,
WebTransportClientIo {
certificate_digest: String::new(),
},
))
.id();
commands.trigger(Connect { entity: client });
Ok(())
}
When you start the server, try using "0.0.0.0:8384" as the address.
(the client still refers to the server as "127.0.0.1:8384"
Even when server is listening to 0.0.0.0, client can't connect to 127.0.0.1 or 0.0.0.0
What is your code to start the server
use std::time::Duration;
use bevy::{log::LogPlugin, mesh::MeshPlugin, prelude::*, scene::ScenePlugin};
use lightyear::prelude::{server::*, *};
use reclipsis_shared::{FIXED_TIMESTEP_HZ, SharedPlugin};
pub const SEND_INTERVAL: Duration = Duration::from_millis(100);
fn main() {
App::new()
.add_plugins((
MinimalPlugins,
AssetPlugin::default(),
MeshPlugin::default(),
ScenePlugin::default(),
LogPlugin::default(),
ServerPlugins {
tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ),
},
SharedPlugin,
))
.add_systems(Startup, setup)
.add_observer(handle_new_client)
.add_observer(handle_connected_client)
.run();
}
fn setup(mut commands: Commands) -> Result {
let server_addr = "0.0.0.0:8384".parse()?;
let server = commands
.spawn((
NetcodeServer::new(NetcodeConfig::default()),
LocalAddr(server_addr),
WebTransportServerIo {
certificate: Identity::self_signed(&["localhost", "127.0.0.1", "::1"])?,
},
))
.id();
commands.trigger(Start { entity: server });
Ok(())
}
fn handle_new_client() {
info!("Got new client!");
// ...
}
fn handle_connected_client() {
// ...
}
Firefox does not work for me, Chromium does.
Funnily enough, native server did see the connection, and it stays open until Firefox process closed.
Oh i just realized i actually have syncthing running on port 8384
unsure if by local you mean native
I'm gonna try a different port
hah whoops. did you accidentally just use the same port?
have you tried enabling the webtransport_dangerous_configuration feature?
i tried using webtransport instead of udp in the simple_setup example and it works for me
normally on the server-side you should have gotten some errors related to certificates
nope, it just didn't connect
maybe the code is waiting for a successful connection event that it never receives?
idk
I get this on my side:
2025-10-25T19:54:54.368160Z DEBUG aeronet_webtransport::server::backend: Failed to accept session: ByError(failed to await session request
Caused by:
connection aborted by peer: the cryptographic handshake failed: error 48: invalid peer certificate: UnknownIssuer
not sure
Bevy's LogPlugin only prints info logs and higher, so that's why I didn't see it
Yeah that probably should be a warn/error level log
merged https://github.com/cBournhonesque/lightyear/pull/1271 which adds lightyear_raw_connection to use the IO layer directly as a connection
Adds lightyear_raw_connection, another Connection layer similar to lightyear_netcode and lightyear_steam.
Clients marked a RawClient will just use their underlying IO (for example WebTransport) as ...
is there an example for setting up an in-process server (but not host-server, separate app with crossbeam channel)
I remember that being a supported or even recommended thing, but can't seem to find the code in examples/common
Maybe take a look at the tests: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/stepper.rs#L143
I think this too: https://github.com/SueHeir/lightyear-menu/blob/main/src/main.rs#L182
Should events like Start be tuple-like or implement Entity::from so they can be used as such?
commands.spawn(...).trigger(Start); // or Start::from
Right now I have to do this, I think?
commands.spawn(...).trigger(|entity| Start { entity });
And I'm finding out I can't pre-emptively add Replicate to entities or else I get a warning "No Server found in the world"?
anyone have an example for scanning for servers on the local network?
With bevy_replicon I believe I can just add their replication component at any time and it works. So that's just kind of nice. But I believe I might just need to change the way I structure things so I can work with that.
Uhh, how do i make the player move in whichever direction they are facing in the avian_3d_character example
my code doesnt quite work
forces.apply_force(transform.rotation * required_acceleration * mass.value());
that's a valid point, that could be added
You need to predict the rotation as I believe in this example player is predicted. There is a function in fps that show you how
The warning is a nuisance but as I did something similar to you I can ensure it means nothing
(Replication still works if sender is active/connected, even if added previous to server connected status)
Ah you're right, it's already handled
I replicate the rotation in actionstate as it is handled by the camera
Ah I see. I wasn't quite sure because there was a bunch of logic in its on_insert hook. Does that mean the warning should be removed?
You are using leafwing?
yep
It should still work perhaps you should check if is receiveing the predicted inputs.
Good question I think yes. As I believe this edge case is handled in another system
I can change them to debug.
Debug logs show this during testing:
transform.rotation = [0, 0.42695636, 0, 0.90427226]
required_acceleration = [0.0000089794175, 0, -0.00004326884]
transform.rotation * required_acceleration = [-0.00002770521, 0, -0.000034427372]
not sure if im even rotating them correctly
Your rotation is a little off it seens
In thesis you can just send the eulerot fields. And them reconstruct them in server side.
That is how I do it at least
well its set by my orbit camera system, so i only need the yaw to be set
alr fixed it
Looks like adding Start to RawServer does not actually trigger LinkStart and thus doesn't start the server? (I got around it by calling LinkStart manually.)
The latest version 0.25.3, has no docs. Would you take a pr to fix that? Or you already on it
Oh I think I just noticed another issue, but it would also explain why Start doesn't not actually start up anything: The ConnectionPlugin(s) are missing when using RawServer.
I also just noticed that the app is crashing because the PeerMetadata resource is missing.
They get added by the NetcodeServerPlugin from the looks of it.
Unsure if they should be moved to ServerPlugins or something else.
Sure, can you put up a PR?
These should be moved to the connection plugin
Not sure what you mean, they are in the connection plugin, but they are not registered since I'm not using NetcodeServerPlugin, which adds the ConnectionPlugins (of which there are three, actually?). So I've just added them manually. Unsure how this is meant to be done.
Got replication working though. Nice!
Predictive spawning is possible. What about predictive despawning?
This command must be used to despawn Predicted entities. The reason is that we might want to not completely despawn the entity in case it gets ‘restored’ during a rollback. (i.e. we do a rollback and we realize the entity should not have been despawned) Instead we will Disable the entity so that it stops showing up.
Sweet.
@pine cape Okey dokey, I added a just doc command so you can also check quickly if your docs are compilable in the ci
@pine cape The testing crate still requires 0.16?
so does lightyear_examples_common?!?!
Despawning an entity that has NetworkVisibility and is non-visible to the client causes console spam.
How to reproduce:
- Sever spawns an entity with
NetworkVisibilityandReplicateset toNetworkTarget::All - Client connects to server
- Server despawns entity without it ever being visible to the client.
- Logs get spammed
Other than the spam nothing seems to break.
Unsure how to "connect" a local/HostClient. Keep getting a "ClientOf 114v0 not found or does not have ReplicationSender" error. I feel like the examples imply that you just add a LinkOf component, and then presumably treat it as any other client? By triggering a Connect event for it?
is there anything in lightyear to sync the server and client Time<Virtual> ?
0.25.3 seems to have docs for me: https://docs.rs/lightyear/0.25.3/lightyear/index.html
Lightyear
what do you mean? it seems to be using bevy.workspace
yep that's it
only the Time<Fixed> is synced via the Tick object
Well, it didn't work when I tried that.
have you tried running the example in HostClient mode?
Other than not being able to control the HostClient, I guess it works.
But it's slightly different since it's Netcode based, right?
Also, how would one connect to a hostname via WebTransport on web? Can't use ToSocketAddrs on web, after all, right?
Thanks for the report; fixed https://github.com/cBournhonesque/lightyear/pull/1275
So it works in the exampels with Netcode but not when using RawServer you mean? Did you try pulling from main again? I pushed some fixes
I think you can just specify it as a string like https://github.com/aecsocket/aeronet/blob/main/crates/aeronet_webtransport/examples/webtransport_client.rs#L92
Well, with lightyear don't you have to specify a SocketAddr in PeerAddr?
ah yes. That doesn't require std though
your compile somewhere still requires 0.16
but SocketAddr doesn't support hostnames
And I appear to be getting the same issues after updating lightyear to latest.
Is there a quick way to have a observer only run if there is a server and it's connected?
the only run condition seems to be is_host_server
check for Query<(), (With<Server>, With<Started>)> in the observer
@pine cape : In case of the examples i mean: https://crates.io/crates/lightyear_examples_common/0.23.0/dependencies This crate is not on the version of the others and depends on bevy 0.16
I switched out netcode for raw_connection in the simple_box example, and yeah, it seems like it works? There's some oddities with the example like being unable to move (host-client) and labels not updating as expected, but yeh.
(The examples are really difficult to use as a reference due to being so fragmented tho.)
I think part of my problem was that I needed the server to be Started before I could connect my host-client to it. Thought that would be guaranteed by queuing the commands up in the right order but no.
only a portion of them broke in this case.
ComponentReplicationOverride is a sample
Having an issue with specifying the spawn location of my player with Transform - I create a player with Transform::from_xyz(spawn_x, terrain_height + 20.0, spawn_z), on the server On<Add, Connected> but it always spawns in with Mut(Transform { translation: Vec3(0.0, 0.0, 0.0), rotation: Quat(0.0, 0.0, 0.0, 1.0), scale: Vec3(1.0, 1.0, 1.0) }) on the client and the server. I'm using AvianReplicationMode::Transform
@pine cape oi you got a laicense to thumbs up me
Log spam still happens on the client using the same setup. Log spam on the server is fixed.
On the client? But the client never receives the entity, no?
Question, adding replicate like to a predicted entity will make it so the other entities will also have predicted components?
Oh, position_rotation_to_transform in lightyear_avian/src/plugin.rs adds Transform::default() if Position/Rotation are present, which are in turn automatically added from RigidBody. Can position_rotation_to_transform only insert the Transform::default() if there is no Transform component already on the entity?
ReplicateLike means that we use the same configuration settings as the root entity (prediction, visibility, interpolation, etc.)
To override but follow the network visibility, what do you recommend?
Great catch, can you make a PR?
The entity never appears to spawn on the client but it seems like its still reacting to a despawn
hmm I guess in thesis they wont have the predicted component so nothing will happen
https://github.com/cBournhonesque/lightyear/pull/1279
Doesn't fix my particular issue but in case it helps anyone else.
Could you try insert_if_new?
updated pr
was just wondering if it helps in your case or not
I'm guessing if there's some shenanigans and Transform is inserted before the command is applied, it could override it (with insert). That was my thinking. (In case anyone was wondering.)
(though I'm not sure if such shenanigans did or even could occur)
Oh I'm still debugging my issue but if i turn off the lightyear avian plugin and mark position and transform as replicated then the player spawns at the correct location and doesn't fall through the ground. I'm trying to figure out exactly what is causing the Transform to be overwritten and it wasn't that particular line
(I added an info! statement to that fn that was never printed)
Is a debug breakpoint on a On<Add, Transform> (on the relevant entity only) possible? (or helpful at all, I haven't done a lot of debugging with Bevy)
I didn't think it would be helpful at finding exactly what is adding the Transform but I'll investigate further
RigidBody adds Transform as well, if that's helpful. I haven't tested the ReplicationMode::Transform, i should add unit tests for it
Oh btw it did fix my issue, I just wasn't applying the patch correctly lmao
i cannot reproduce this in my unit test
@pine cape This is ugly
fn replicate_body_part(
query: Populated<(Entity, &BodyPart), Without<ReplicateLike>>,
replicated_like: Query<&ReplicateLike>,
mut commands: Commands,
) {
for (entity, body_part) in query.iter() {
let body = body_part.get();
debug!("{},{}", body, entity);
if replicated_like.contains(body) {
commands.entity(entity).insert((
ReplicateLike { root: body },
PredictionTarget::manual(vec![]),
InterpolationTarget::manual(vec![]),
));
debug!("Added replicate like to {} according to {}", entity, body);
}
}
}```
What do you say if we add an option to only replicatelike visibility?
@pine cape Any chance you would know why HostClient Inputs are throwing this error on a connected client with rebroadcasted inputs? (on v0.25)
2025-10-28T15:50:05.073529Z WARN bevy_enhanced_input::action::fns: action `CameraForwardAction` (`362v0`) expects `Axis3D`, but got `Bool`
2025-10-28T15:50:05.073541Z WARN bevy_enhanced_input::action::fns: action `MoveAction` (`364v0`) expects `Axis2D`, but got `Bool`
I also see mapping errors for the host actions on the connected client initially, but then the errors go away when the BEI errors show up.
Additionally I'm seeing mapping errors on a second connected client. I think it's failing to get the other client's actions.
2025-10-28T15:53:36.304351Z WARN lightyear_serde::entity_map: Failed to map entity 552v1
2025-10-28T15:53:36.304362Z WARN lightyear_serde::entity_map: Failed to map entity 554v292
2025-10-28T15:53:36.304366Z WARN lightyear_serde::entity_map: Failed to map entity 515v1
2025-10-28T15:53:36.304369Z WARN lightyear_serde::entity_map: Failed to map entity 516v1
2025-10-28T15:53:36.304372Z WARN lightyear_serde::entity_map: Failed to map entity 553v12
2025-10-28T15:53:36.304449Z ERROR lightyear_inputs::client: received input message for unrecognized entity entity=PLACEHOLDER target_data.states=BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Bool(false), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } end_tick=Tick(21374)
2025-10-28T15:53:36.304469Z ERROR lightyear_inputs::client: received input message for unrecognized entity entity=PLACEHOLDER target_data.states=BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Bool(false), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } end_tick=Tick(21374)
2025-10-28T15:53:36.304479Z ERROR lightyear_inputs::client: received input message for unrecognized entity entity=PLACEHOLDER target_data.states=BEIStateSequence { start_state: ActionsSnapshot { state: Fired, value: Bool(false), time: ActionTime { elapsed_secs: 10.265625, fired_secs: 10.265625 }, events: ActionEvents(4) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } end_tick=Tick(21374)
2025-10-28T15:53:36.304489Z ERROR lightyear_inputs::client: received input message for unrecognized entity entity=PLACEHOLDER target_data.states=BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } end_tick=Tick(21374)
Yes it's due to an implementation detail, it should only have that at the beginning while the entities are being replicated. Entities and Inputs are replicated through different channels
Normally you can ignore these errors
So maybe some bools inside ReplicateLike that specify which things we inherit?
I see the warning spammed continuously.
Should it only be happening initially?
Yes, have you tried it in the BEI example?
I have a feeling I'm missing something, especially since the second client is throwing continuous mapping errors.
I'll have to get set up with the example again, but I'm trying to diff the several different examples (projectiles, BEI, avian 3d character) against our code base to figure out what's missing. for example I added the LightyearAvianPlugin and added the ActionOf to type registration.
I was trying to replicate my setup a bit in the BEI example, but it looks like I don't need to change anything for the second client to fail to get Actions from the other client
All you would need to do to replicate is launch a host-client and attach 2 additional clients.
@pine cape Do you disable transform propagation for child entities of predicted physical entities?
@pine cape Also the projectiles example has errors right away when running in HostClient mode
That's at tag 0.25.3, lemme check main.
Yeah, same on main.
Are you supposed to manually start a local server (with no io/connection layer) by just adding the Started component? That's what I'm doing and it seems to work, but using the Start event doesn't seem to do anything.
yeah i don't think the projectile example is compatible with host-server
i guess? I've never tried setting up a server with no io
it depends on the replication mode; but in general no i don't touch the transform propagation logic
I just tested in the BEI example and host-client input replication is working properly. An easy way to see if it's working is to check the number of rollbacks in the PredictionMetrics resource. If the inputs were not replicated, the client would always be rolling back whenever the host client is moving.
For instance if you set the input delay to 0 ticks, you can see that there are rollbacks despite input rebroadcast, because the client doesn't receive the host's inputs in time to use them for prediction
Is this with a host + 2 clients? On tag 0.25.3 and main the clients do not get the actions of one-another. See the inspector in this screenshot, the clients only have 2 movement actions, not 3 like they should.
It does work fine with HostClient and a single client.
This is essentially the inverse of the problem before 0.25. Before then, clients would get eachother's actions, but wouldn't get the host's. Now clients get the host's actions, but not other clients.
I'm specifically saying actions as in the Action entity is not replicated from other clients, input rebroadcasting seems to be fine (hence the errors in the log).
upgrading to 0.25 was pretty easy and prespawned seems to work like I hoped now 🎉
got it, will open an issue
I was wrong by the way. I don't think it's possible to start up a server without io. It appears to start up and then immediately shut down afterwards.
(Well, my "workaround" did.)
inserting (Linked, Started) works!
Presumably because theres some code that checks for Unlinked, Started and then transitions the server to Stopped.
Hmm now my native client doesn't want to connect. (web client connects fine)
"invalid remote address: [::1]:13580" hmmm
alright, it works with ipv4
so localhost resolves to ipv6
I guess if you connect via IPv6 when the endpoint is IPv4 there is a problem?
https://github.com/quinn-rs/quinn/blob/491b8b5deb14ff5c1e4ba709b7936855e63aeef5/quinn/src/endpoint.rs#L214-L216
Which I think means.. if ToSocketAddrs returns both you should probably prefer IPv4 results, because those can always be upgraded to IPv6?
Now I'm stuck with inputs. buffer_input isn't called. I've followed the tutorial, not yet doing any prediction, but I wonder if I'm missing anything.
Not entirely sure what causes InputMarker to be inserted, if I need to do it myself, or which entity would even be the correct one.
@pine cape why did u switch the all the examples to use webtransport by default instead of udp
@pine cape Will the replicated child entities components arrive at the same time as parent. If they are in the same replication group?
I ask because I ran into some issue here
Yes that's the reason for ReplicationGroup existing
No reason, I was just testing stuff with webtransport
You usually want to control the predicted entity, check the simple_box example
Sure, usually, but I figured I'd try it without predicting first. Should it be possible to control an entity such that it only moves server-side?
I guess my mistake was the fact, that I inserted my component a bit after e.e
Yeah just add the InputMarker on the replicated entity without adding Predicted
And then add the input systems only on the server
@pine cape Question if client is not the replicationsender, is there an efficient method to first mutate on client and them inform server. Besides events and so on? Or do I just make it so he is a replicationsender
Either you use messages, or inputs, or ReplicationSender
Question does inserting a component on server/replication sender, will it trigger a On<Replace> event on client? It seens to be only mutating the inner field
I only mutate if the component is already present on the receiver side; but I could also just re-insert
maybe this can be added in the ReplicationConfig
I see
that would avoid one line of code to me so yes
I'm still really confused. Trying to wrap my head around some of the examples. InputPlugin::<Inputs> is being registered, but I can't for the life of me figure out what causes ActionState and ActionMarker to be added.
ActionMarker appears to get added to predicted players but it already requires there to be an ActionState.
I only see it being added explicitly in the spaceships demo.
(or the search on GitHub is being weird)
There's confusing comments, too.
Add an ActionState component on the Client entity to send inputs to the server
but the function does no such thing (explicitly)
Maybe the non-native input implementations (leafwing, BEI) do it automatically, but then I'm still curious how it decides which entity to add the input components to.
Could someone help, when a second client joins, no changes from one client replicate to the other.
I'm building a game on top of the avian_3d_character example.
I logged the transforms of characters in the apply_character_actions function, and each client only prints out their own transform (even though rebroadcast_inputs is set to true), while the server prints out both character's correct transforms.
replication_mode is set to transform
From what I understand, only the local player is predicted on a client, everything else is just replicated from the server. See https://github.com/cBournhonesque/lightyear/blob/main/examples/avian_3d_character/src/client.rs#L29, it uses the With<Predicted> filter.
Or... not? It creates a PredictionTarget for the owning client (plus comment) here but that ends up unused, and it inserts PredictionTarget::to_clients(NetworkTarget::All) later.
@pine cape Hmm are you disabling sleeping plugin of avian? Is that still necessary? I question becauser the flickering on debug mode annoys me
i cant connect to my remote server but im not sure why, server is setup on UNSPECIFIED, and the client goes to the public ip and same port but it just says its an unreachable network, but when i try it on my own pc i can connect to LOCALHOST without issue, any clues as to why this could be happening?
You didn't mention you forwarded the port. Did you?
InputMarker is added manually: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/client.rs#L113
which adds ActionState and InputBuffer via required components
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
Hello, may I ask what is the correct way to implement multi connection mentioned in the book ? I tried having multiple Servers with separate IO components, but this seems to stop replication using ReplicationMode::SingleServer. Having multiple IO components in a single server does not seem to be possible since they each need a different LocalAddr.
are the ports open/forwarded on the remote server?
If you use multi server entities; you cannot use ReplicationMode::SingleServer, you need to explicitly specify on which server you're doing the replication.
But multi-server entities is not fully supported, a lot of systems only work with a single-server.
What kind of multiple servers do you need?
What is possible right now though is to have a single Server entity that has both SteamServer and NetcodeServer at the same time
I was trying to make a NetcodeServer receive both udp and websocket connections.
It's not a must. If there isn't a simple way for this I'm fine with just using one type of connection.
Yes this is a usecase that should be supported (and was in the past).
I think the solution would be to use ReplicationMode::Target, which should figure out a mapping from the PeerId to the Server entity corresponding to that peer
yes
yes
The ReplicationMode::Target seems to be marked as a todo (github). I assume I'll have to wait for the updates. Thanks for the help!
And how does it get added on the server?
the server receives an input message from the client and insertes the ActionState and InputBuffer: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_inputs/src/server.rs#L259
Does that mean a client could theoretically send input for any arbitrary entity?
I don't see it checking for ControlledBy or anything.
(but even then, I suppose it could insert any registered input onto an entity that isn't supposed to have that type of input present?)
Ngl, inserting components not triggering on insert events but triggering on remove is a little weird.
yes that's right. In a production game the server would have a validation layer to check that the inputs come from the correct clients/entities
Would in not make sense to explicitly add the right input component and automatically check for it and whether ControlledBy was present on the target entity and pointing to the client? Or what is the purpose of ControlledBy?
And where would the validation layer go?
on the server
Am I supposed to write my own receive_input_message-like system?
no, it's handled for you
I can't find anything. How would I go about that?
Do what? https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/client.rs#L80
Any modifications at tick T on the ActionState will be also applied on tick T on the server
I can't check for modifications on ActionState itself because I won't know which client they came from anymore.
I'd have to get between the message being received and the change being applied, no?
You can attach a component on the entity that contains the PeerId; or you can use the ControlledBy entity
Honestly, a rather small modification to receive_input_message would do exactly what I'd want.
- Just before here I would check if the entity has a
ControlledBycomponent and ifowneris the client who sent the message, otherwise reject it with a warning. - Modify the query and require an
InputStatecomponent to be present on the entity. The existence of that component would mean that it's supposed be able to handle and process that input. Without it, the client did something it wasn't supposed to. (This would require changing the examples and documentation to match.)
I can open an issue if you think that's reasonable.
I don't really want to make ControlledBy required for Inputs, as the entire notion of 'control' is optional.
Also you would require users to add an InputState component on the receiver-side? that's also a pretty big ergonomic hit.
In production you would need to have a more involved input-validation layer/system that would go further than simply checking for some components being present
I don't see what problems you're facing right now. Are you worried about cheating?
I'm not sure I'm using the term correctly but it just feels unsound.
Clients sending arbitrary input to arbitrary entities is just something that shouldn't be happening in my view.
And there should be an easy-to-use contract that is enforced. And the building blocks are already there.
I've been digging through the code for 3 days trying to figure out "how is the server defining that this entity is supposed to receive input?" and in the end I was perplexed that in fact, it did not at all.
It's my understanding that, on the server, an entity needs to have something like PredictionTarget pointing at a client, in order for the server to listen to the actionstate component updates coming from that client, for that entity. I've just figured this out through trial and error, but I might be wrong. Also, I'm using Leafwing, not sure if this applies outside of that
yes there is no input validation currently because i don't think it's something that's important enough to be deal with right now.
If someone has a live game with users where there is a potential issue of users cheating by sending inputs for the wrong entity, then we can focus more on that
I'm trying to build my understanding bit by bit so I currently use native input and no prediction at all. Though adding prediction would be my next step.
That's wrong, the client sends inputs to the server for entities that have the InputMarker component. This can be a predicted entity, or any other entity.
That marker is inserted automatically for you if you add a leafwing::InputMap component on an entity.
Input validation is quite easy no? If a server get the action triggers and checks if they are absurd that should be more than enough for a lot of hacks
Oh interesting, I'm using Leafwing and lightyear, and I have input replication working without any inputmarker components
yeah, i think the solution would be to put a hook where the user can provide their own function to validate the input and check if it should be rejected or not.
Then the user could add whatever logic that they want. I would accept a PR that does that.
Aeronet does something similar to accept incoming requests: https://github.com/aecsocket/aeronet/blob/main/crates/aeronet_websocket/src/server/config.rs#L23
But the function only has access to a limited amount of data. Ideally that system could fetch any ECS data.
Input validation to me is what happens in the shared function that applies the input. This is more like ... validating that the input message is received by the correct entity. (Which I still don't know how to do.)
Heck, even better. Some input validation should be done when the type is being constructed in the first place. "Parse, don't validate."
yes it's not possible to do right now. The direction I want to take this is to let users define their own validation hooks
What's the purpose of ControlledBy?
The documentation suggests that it lets a sender "control" the entity.
Is it only to replicate a Controlled component?
(and, well, despawn it automatically, if requested)
The main purpose is:
- add a
Controlledcomponent so that clients can easily filter for entities they 'control' - when a client disconnects, automatically despawn their controlled entities
The naming and wording, while not explicit, does imply that it could have a hand in how input is being handled. At least to me. Perhaps OwnedBy and Owned would be clearer, if that was the intention. (Unsure if that's already in use anywhere.)
Also, lightyear claims it is "server-authoritative". What is authoritative about letting clients arbitrarily send input to any entities by default? When would you ever want that if you're building or using a library that makes such a claim?
I'll make an issue for now.
Wait a moment, there was something I was confused about previously.
Some of the examples use an enum for their input types, right? Does that mean it's not possible to both Move and Shoot at the same time? And how does it represent no input?
You can easily distinguish or input types in bei, in leafwing is more of a pain
I'm using a struct in my case but I'm using the native input system.
Okay yeah leafwing is doing a hashmap behind the scenes.
I think I understand, then.
ReplicationMode::Transform seems to have issue right now. I've only tested ReplicationMode::Position.
Will open an issue to add some unit tests for ReplicationMode::Transform
this should solve the BEI input rebroadcast issues. https://github.com/cBournhonesque/lightyear/pull/1286
I tested with a host + 2 clients
Awesome. Thank you! I'll check it out.
seems the distributed authority example is broken rn btw
Yep it hasn't been updated. I think authority in general needs more work
I wonder, is there a good reason why client and server have different systems that run the same shared movement function? Why not register this once, say in the protocol plugin?
pub fn movement(
time: Res<Time<Fixed>>,
mut players: Query<
(&mut Transform, &ActionState<Inputs>),
// Must be a `Player` which is either be `ControlledBy` a remote
// client (server-side) or its movement `Predicted` on the client.
(With<Player>, Or<(With<ControlledBy>, With<Predicted>)>),
>,
) {
Yeah in some cases it should be possible to have a shared system for both client/server
@pine cape Was there recently a solved bug when it comes to entity maps? I ask because it seens everytime I add a mapable component via observer for a certain scenario of mine it seens to not be able to map. It is oddly specific so I am not gonna go into the details just curious
Is nothing too worrying it just fails to map one very specific replicated entity
Nothing changed; maybe you need to use add_component_map_entities instead of add_map_entities if the map entities implementation comes from the Component trait instead of the MapEntities trait
@pine cape Ought oif curiosity why we only try to map anm entity for a few frames and not continously?
Btw this bug is truly insane, I can show you my pseudo fix if you like is quite fun, in a vc
In summary, when I make an entity. In an observer, that has a component that should point to another mapped entity. It does not find in remote map
But if I add on a system in any step of the way on insertion it does
well this destroyed my sunday
how does the MissingDeltaFns error get fixed? Do specific types of deltafunction need written for each component?
also is there a downside to use it on pretty much everything?
oh I see the example, you need to impl Diffable
I need to send a ConnectToken and the server address + port together to be able to create a valid Client? The ConnectToken can be valid for multiple addresses if I'm reading this correctly?
Yes correct
@pine cape I might be misimplementing replication groups, would you kindly give me a short step by step on how to do so? I am just adding the ReplicationGroup component unto my entity. Is there anything else? btw replication groups seems to not use replication groupos
You just add ReplicationGroup on an entity; all entities that have the same ReplicationGroup are replicated together
@pine cape Found out what was the issue with map entities, if you map an entity previous to it being replicated. It fails to map it and since we dont continously attempt at mapping well it dies
What is your take on this, want me to make a pr fixing this interaction?
Also is there an issue already opened it seens map entities has been a theme of discussion on the repo issues
I guess I'd like to know more about the usecase. How come you're mapping an entity before it is replicated?
any idea what's causing this? do i need to set a retention period for history?
I have a relationship that was built before I replicated, if I replicate that relationship well it just fails to map 100% of the times
Are you replicating a lot of new entities? I don't think i properly clean up stuff in ReplicationSender and ReplicationReceiver
Did you add a ReplicationGroup on all entities of the relationship?
nah i only replicate the players when they join but this graph was on the server overnight when i wasn't connecting any players
I added ReplicateLike
Can you provide a unit test or MRE?
I will open an issue
What's the appropriate way to synchronize first-person camera orientation?
Sure
Is it predicted?
Well you wouldn't want your camera to jitter because of network issues or anything, so I'm thinking it should be entirely client-authoritative. But I'm not sure how to go about that. Is camera movement an input? Is it relative (mouse movement) or absolute (yaw, pitch)? Or should it avoid relying on the input system altogether?
The only place I can see camera orientation affecting gameplay, other than visually (player head rotation), is when placing things in the world, it might affect the placed object's rotation. That could in theory be encoded in the action tho.
Use input system, fps shows you how
@unkempt sedge Where? From what I can tell, all the important stuff runs in (Pre)FixedUpdate. How would that even translate to responsive first-person camera controls?
Well in summary, you translate the given rotation from client (the rotation input) in server. Server only redo the action, but can still check if is absurd
The example still fails to really show how it would be done in practice. Like, do you apply the change from AccumulatedMouseMotion to the camera orientation directly in Update and also accumulate it so it can be sent at as input action in FixedUpdate?
@pine cape Made a mre in the examples crate here, just run the just commandjust mre https://github.com/Baker-games/psycho_duel. Btw I did this to have a very quick mannerism to show the weird issue I come up while using your crate
Does it make any sense to have Server and Client on the same entity if you are just doing a host client?
I'm assuming it probably shouldn't make a difference, just makes it a bit easier to do some bookkeeping
In the fps example, the action state sends the position of the cursor using ActionState in PreFixedUpdate, so that the server has access to that at every tick.
You should be able to use something similar to send any kind of camera-related information
I think it should be possible, but i haven't tested it
i don't see mre in https://github.com/Baker-games/psycho_duel/blob/main/justfile
do you have something lighter?
the repo itself takes a lot of time to just download
Ah yes the history keeps track of assets and is quite chonky, I am gonna make it easier for cloning. I will ping ya when I am finished
Upd. sorry, disregard this
@pine cape hi! another quirk I found with doing .remove::<Replicating>().despawn() is that a server will try to replicate removal of components. It's not replicating the despawn, but I guess since despawning also means removing the components, somehow the absent Replicating isn't enough to prevent the server from sending remove entity actions
I extended the log statement a bit to debug-print the actions:
// safety: we know by this point that the entity exists
let Some(local_entity_mut) = remote_entity_map.get_by_remote(world, entity) else {
error!(?entity, ?actions, "cannot find entity");
continue;
};
and I'm getting the following:
2025-11-06T23:14:05.625194Z ERROR lightyear_replication::receive: cannot find entity entity=108v1 actions=EntityActions { spawn: None, insert: [], remove: [2, 3, 4, 6, 7, 8], updates: [] }
Oh, nevermind, my bad, I actually broke the code in my lightyear fork, the main branch is good
I have something else though ☺️
https://github.com/cBournhonesque/lightyear/pull/1292
Regarding the room system, it seems that when an entity is removed from a room, the clients in that room always lose visibility to it. Wouldn't this be a problem when a client and an entity share multiple rooms?
Hm you're right. Could you open an issue for it? It might actually work, but let's check
If I want a user to be able to connect to different servers is it best to despawn the client on disconnect and create a new one when the user switches to a new connection?
it's up to you! normally keeping the same entity should work
This is working well. Thanks!
For deterministic replication should a checksum mismatch cause a rollback on the client? Even if I turn on state checks, no the client never rolls back for a checksum mismatch
Hm no it means that something has gone very wrong. It compares checksum at ticks that were already received and processed by all clients, so it's mostly to catch non-determinism bugs
@pine cape If it isnt much trouble would you make a release introducing avian 0.4.1, I can open the PR if you like. (A hook bug broke my collider e.e)
I can do it sometime this weekend
If I'm using CrossbeamIO for Client & Server app spawned in a separate thread, do I still need to trigger Connect on the client?
I have a set up which works for Client connecting to local Server using UdpIO, but the same configuration doesn't work if I swap out for CrossbeamIO components - when the Client tries to connect it times out.
Yes you still need to trigger Connect
I use crossbeam in tests; maybe you can take a look
I've been using this for a bit and it looks like client to client is just fine now.
I am, however, noticing a difference with Host inputs. I don't have a repro on an example yet, but maybe you'll have an idea what's going on? Basically, the host looks like it's potentially missing some inputs or missing when the inputs end. For example, my player controller will repeatedly look like it's trying to continue moving in the direction the host was moving before they stopped for a little bit. Other clients are flawless, it only affects the hostclient on clients. It's the exact same code path for my systems for both regular client and hostclient for the player controller and input handling systems, so I'm leaning towards it being BEI/Lightyear.
In this video the first client I'm controlling in the bottom left corner has smooth animations, smooth turning, stops when the input stops, etc. However, when I start moving the HostClient around (top right) you can see on the clients the turning is jittery, there's continued movement and corrections when the player stops.
I got it working, had assumed that the LinkOf entity would be created on the server automatically when a client connects with CrossbeamIo as ServerUdpIo seemed to do, but examples created it manually so I did that.
are custom messages still a thing in the latest lightyear?
trying to send some voxel command (set sdf to X voxel, send chunk, etc.) stuff over the network on an unordered reliable channel, but having a bit of trouble figuring that out
hm i'll have to check, And you have rollbacks enabled?
I have been testing with enough input delay that the host client inputs always arrive on time on the remote clients, but if it's not the case it should be handled via rollback.
Yes, see here: https://github.com/cBournhonesque/lightyear/blob/main/examples/simple_box/src/server.rs#L94
https://github.com/cBournhonesque/lightyear/blob/main/lightyear_tests/src/client_server/messages.rs#L50
ooo thank you!
Yeah I have rollback enabled. That's using the balanced preset I believe, and I did try adding more input delay with no noticeable improvement. There's no link conditioning going on in that recording, but it does get noticeably worse with high latency/jitter/loss.
i would always add link conditioning as testing with no latency is not realistic.
So I've been trying to solve this jitter on my characters when they dash, it only seems to happen when the character speed changes, and one fix I've found that works is sending the player's speed from the client as an input, and using that input to decide how fast the server moves the character. But I don't like this solution for authoritative-server reasons.
Could I get some information how leafwings Action state works in reguard to input delay?
Right now I have this on the client
InputTimeline(Timeline::from(
Input::default()
.with_input_delay(InputDelayConfig { minimum_input_delay_ticks: 7, maximum_input_delay_before_prediction: 10, maximum_predicted_ticks: 0 }),
)),
and am using the following to see the actions of the player on both client and server
&ActionState<PlayerActions>,
One thing I noticed is that movement speed updated directly happens before the movement speed over input changes, and I think this is the cause but I don't know what I should do to fix it
I had a similar issue, a good solution I found was to make it so the system responsible for enforcing the dash force was put in shared with a fixed updatw system. I also made it so it always ran in a chain after movement as that ensures the ordering between client and server systems is not differente.
I am guessing that is what causes that jitter (rollback)
What part is the jitter? can you also attach what it looks like on the main client to see what it's supposed to look like without networking?
Do you have prediction enabled for the velocity?
InputDelay just means that the input is sent immediately to other players, but becomes active locally with some delay, so it helps removes some of the latency for predictioon purposes.
At the very end of the dash the character seems to like teleport back, (it doesn’t happen every time)
I have prediction enabled for velocity, no correction though.
Does the server and other players use their received input immediately? Or does the input stay queued in a buffer until the correct tick is reached?
What if the dash is a velocity change and not a force? I’ll try system ordering and see if that fixes it!
Inputs are put in a buffer and are applied only on the correct tick.
I still dont see in your video at which point there is an issue. What is the exact timestamp?
heres a better video of it!
look at 0:06 to 0:07
this is when I take the player speed on the client and buffer it into the inputs and use that :
app.add_systems(
FixedPreUpdate,
update_current_move_speed
.before(InputSystems::BufferClientInputs)
.in_set(InputManagerSystem::ManualControl),
);
pub fn update_current_move_speed(
mut action_state_query: Query<
(&PlayerSharedMovement, &mut ActionState<PlayerActions>),
(With<Predicted>, With<Controlled>),
>,
) {
for (movement, mut action_state) in action_state_query.iter_mut() {
action_state.set_value(&PlayerActions::MoveSpeed, movement.walk_speed);
match movement.movement {
PlayerMovement::NoMovement => {}
PlayerMovement::AWSD => {
action_state.set_axis_pair(&PlayerActions::Direction, Vec2::ZERO);
}
PlayerMovement::Direction(vec2) => {
action_state.set_axis_pair(&PlayerActions::Direction, vec2);
}
PlayerMovement::Target(vec2) => {}
}
// action_state.set_value(&PlayerActions::MoveSpeed, movement.walk_speed);
}
}
I don't think it's related to player speed. Basically you're seeing a rollback?
I would first try to understand what's causing a rollback, and if the client and server are seeing the same inputs at the same ticks.
This is not using HostClient mode, right?
Maybe try enabling debug logs for lightyear_prediction::rollback and add some logs similar to what I have here:
https://github.com/cBournhonesque/lightyear/blob/main/examples/avian_3d_character/src/shared.rs#L187
Normally you shouldn't have any rollback if other players are not involved
got it working :D, didn't realize i needed my receiver to be in Update not PreUpdate:
2025-11-10T02:26:31.886671Z INFO arch_core::voxel::commands: Received message: SetVoxelsSdf { origin: IVec3(763, 45, 11), sdf: Sphere(Sphere { radius: 5.0 }), voxel: Air, params: SetVoxelsSdfParams { within: 0.0, can_replace: VoxelSet(65529) } }
Your receiver can be in PreUpdate, after MessageSystems::Receive
Is there a marker component for something like With<LocalClient>? I see HostClient, but I'm not seeing anything special on my client side. I added my own LocalClient component and do Or<(With<HostClient>, With<LocalClient>)> for now
On the client you should just have a single Client entity
hey @pine cape thank you for your work on lightyear, i opened this one liner pr because the wasm builds are broken for me since it includes the server code, could you take a look? https://github.com/cBournhonesque/lightyear/pull/1298
oh my gawd your profile pic is so cute
@pine cape Is there a slight change that a input may never be received due to packet loss and so on, example: A system like this to never receive it is input message
fn remove_walking(trigger: On<Complete<MoveInput>>, mut commands: Commands) {
let player = trigger.event().event_target();
commands.entity(player).remove::<Walking>();
}```
Hello all 🙂
Does anyone of you have mixed : https://github.com/idanarye/bevy-tnua with lightyear ?
For now I'm using leafwing-input that are "nativelly supported" by lightyear and it works fine.
But as Tnua already have plenty of logic that I wouldn't like to redo I was wondering how easy it's to integrate with lightyear.
There is a chance if the client is not ahead enough of the server. You can control the settings in the InputConfig (I'm on my phone but I can give a link later)
If you gonna point me to input delay I will stab you
voxel command sending is working now 🙂
A follow up on the client log spam on non-visible despawn, it appears to happen when a non-visible child entity is despawned.
I don't really appreciate this kind of comment. I was referring to SyncConfig: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_sync/src/timeline/sync.rs#L87
nice! are you using the bandwidth limiter?
is it set up by default for messages somehow? If not no, the last portion lagging a bit is because I have a separate limiter on remeshing stuffs
I'm not super familiar with tnua, and I don't think there has been any examples of an integration between tnua and lightyear. I think @opaque wing was exploring some refactor to make it easier
Here is a video clearly displaying odd input snap behaviour with logs: https://streamable.com/d366vq. In the video I ran the exact same executable twice, the first run worked flawlessly, the second run had buggy input behaviour without any apparent rollbacks or corrections.
Relevant Info:
-
TPS is set to 5
-
The ship's rotation is determined by
Rotation(Quat)which is predicted and correctly registered with FrameInterpolation. -
During non-buggy runs, my client logging system appears to print twice in the same tick despite only 1 player being present.
-
During buggy runs, my server logging system spams: "bevy_enhanced_input::action::fns: action
protocol::player_action::MovePlayer(213v0) expectsAxis3D, but gotBool" -
Move action is declared like:
#[derive(Debug, InputAction)]
#[action_output(Vec3)]
pub struct MovePlayer;
And spawned like:
commands.spawn((
ActionOf::<PlayerContext>::new(player),
Action::<MovePlayer>::new(),
bindings![
(KeyCode::KeyW, SwizzleAxis::YXZ), // +Y
(KeyCode::KeyS, Negate::all(), SwizzleAxis::YXZ), // -Y
(KeyCode::KeyA, Negate::all()), // -X
KeyCode::KeyD, // +X
(KeyCode::KeyQ, Negate::all(), SwizzleAxis::YZX), // -Z
(KeyCode::KeyE, SwizzleAxis::YZX), // +Z
],
));
- The attached image is my Prediction and Input config. I have tried tweaking the config in an attempt to fix but have not succeeded.
A video of the behaviour with a different config: https://streamable.com/5hpyjp . With this config the behaviour is a lot more consistent and rarely "snaps" but the buggy jitter almost always happens and self corrects overtime. Perhaps my SyncConfig needs tuning? The attached image is my config:
No it's not setup by default. It's applied per Link (on the sending side) for example like this: https://github.com/cBournhonesque/lightyear/blob/main/examples/priority/src/server.rs#L60
You can send a message with a priority value to control which messages should be prioritized
Fixed here: https://github.com/cBournhonesque/lightyear/pull/1299
Thanks for reporting this, this was a pretty impactful bug
During buggy runs, my server logging system spams: "bevy_enhanced_input::action::fns: action protocol::player_action::MovePlayer (213v0) expects Axis3D, but got Bool"
hm i'm wondering if some input mapping failed and the inputs are being sent for the wrong entity
Could It be because I have multiple entities with ControlledBy set to my client entity? I'll try removing those and retest.
not sure; do you have an easy repro?
Not at the moment. I'll try to make one tomorrow
Similar logs can be seen in this video
Axis2D and Axis3D seem to be causing the issues, my boolean inputs don't seem to have any jitter or snap problems if that helps
In their case the got Bool log appears only once at the beginning, which is expected.
But you have it constantly, which means that the input is somehow wrong.
I am sorry, wont happen again
Yes, we have integrated tnua with Lightyear in our game. We used to have Leafwing, but have since moved to BEI. You can see the current status in the video I posted above:
#1189344685546811564 message
For actions that a player can make, that should only be fired once, and I don't want to accidentally trigger two frames in a row, how should they be handled? Should the shared system that consumes the action just reset the ActionState? (This is without BEI or leafwing.)
Can you reproduce this in the BEI example? It's hard to debug without having a repro
Hm there is no built-in functio ality. Maybe you can compare with the previous value in the InputBuffer?
Oh nice ! 😄
Do you have some links to documentation or examples ?
Do you have pros and cons for leafwing vs BEI ?
From what i'm seeing here, I could use whatever input I want as long as I use tnua basis... https://github.com/idanarye/bevy-tnua/blob/main/examples/example.rs
Also I guess TnuaController is only client side and TnuaAvian3dSensorShape is both client server, and then same logic etc..etc.. for normal movement with lightyear ?
(sorry for the tons of questions, thanks for your help ! 🙂 )
BEI is about to be upscaled to bevy, according to a vote. I guess leafwing will in general be less and less mantained as a result.
If a client joins a server, the Transform of other players still seems to be at the origin, as long as they don't move. How could this be? Is it something with my Player component requiring a Transform?
My architecture is probably closer to the projectiles example, but that wasn't working with HostClient for some reason. It might be better for me to make a fully predicted Tnua example judging from the message above as well.
Unfortunately I don't have a public example, but since I've ran into some edge cases with HostClient I'll probably be setting up a Lightyear example that matches my architecture.
Tnua is expecting a more "pull style" architecture since it's feeding everything into the basis at once, so in that sense it's easier to integrate Leafwing if all you care about is the player controller. That said, BEI is better for literally everything else because of its flexibility and "push style" architecture with observers. Unfortunately I ran into inconsistency with pull-style in BEI even though it's technically supported. It looks like BEI is a major contender for upstreaming, so I bit the bullet and made the switch. What I had to do to feed the basis was to create an "input accumulation" component, populate that with observers and clear it after reading it in the controller system.
RigidBody inserts Transform::Default. Does your entity have Position and Rotation?
I'm using pull style with BEI, I think it works fine
No physics, so no separate Position/Rotation.
(Currently I'm just syncing Transform directly.)
Transform is probably being added by an observer or required components
How can i sync timers? When i replicate a timer it starts new. I thought about just using time and do the logic itself. Is there a time resource that is synced between server and client?
The safe alternative is to base your system on ticks instead of timers (something like after 12 fixed ticks despawn or do action) as server/client have distinct time deltas, it is also easier to replicate and ensure determinism:)
But replicating a timer should just work in my opinion if it is not something might be wrong
2025-11-13T12:07:56.710014Z INFO lightyear_prediction::despawn: inserting prediction disable marker self.entity=758v1 @pine cape Mind if I downgrade this log he is quite spawmy (occurs everytime i call prediction_despawn )
May I ask how a required component would cause another component to not be synced initially?
I understand an observer doing it.
A required component shouldn't override a component if already present.
And a component already being present (due to required component inserting it) should not prevent replication from updating it.
yep let's do that
It's just a hypothesis. Maybe the server replicates E without Transform to the client. The client inserts some rendering related component which adds Transform::Default().
Than server adds Transform to the entity and starts replicating it
I'm not really doing anything fancy. Here's my code if you'd wanna take a peak: https://git.mcft.net/copygirl/bevy-bloxel-classic
@pine cape Bevy metrics dashboard is causing the crate to compile 0.16.1, which in turn is causing the ci to fail on docs
Mind if i remove it :?
@pine cape So my game has a teleportation logic, where you kinda teleport player when he changes levels. Since he is predicted he starts to rollbacks to previous states he was in whenever I teleport any ideas on how to avoid this nuisance, is there any componnet that just disables prediction
Q: what's the best way to avoid host-client setup (as it gives me too many inconsistencies/edge cases)? run two separate bevy apps in different threads/custom loop? crossbeam to link like the tests use?
I found the source of the BEI spam/jitter seen here: having both the "lightyear/server" feature and the "lightyear/client" feature enabled at the same time.
Using only one at a time produces either no warnings or 1 warning.
CrossbeamPlugin <-- looks simple enough
yep, we can remove it, good point
there shouldn't be any rollbacks for a teleport, if the teleport logic runs on both the client and server. You should try to understand why there is a misprediction in that situation.
But you can add the DisableRollback component to temporarily prevent a predicted entity from being affected by rollbacks
Yep I think the easiest is two apps (one per thread) and a CrossbeamLink between the two
Thanks 🙂 im starting to lean towards host-client again.. even with some edge cases, it seems to be a better overall choice (eg singleplayer wont have 2x (and more!) copies of entities and components)
Maybe you had a system that was running twice because both client and server were enabled. InHostServer mode you have to be careful that only one of the systems (either client or server) runs
SG! Let me know what kind of issues you run into; I agree that it is kind of tricky to make it work, since you always have to think about edge cases
hello everyone, encountering a bit of a weird thing, any help would be appreciated
i'm setting up a project right now using lightyear and hostclient mode.
i have two buttons, one for hosting and one for joining.
when you click hosting, it spawns a server and a client, starting the server and connecting the client.
then in a second instance of the game i click on join and it spawns a client that connects to the server.
this works.
then when i disconnect, i despawn the server and/or client after disconnecting client and stopping server.
when i then click on host again (spawning another server and client), it seems to remember players/connections from before (?)
am i misunderstanding something, doing something wrong, or missing something? what's the intended way on doing this?
okay, i think i fixed it by starting/stopping the server but keeping it spawned in, and the client gets spawned/despawned.
The logic wasnt shared, it was more of an egui message being sent by client and consumed by server. I made it so is shared there is still one rollback sometimes but is way better than 20
@pine cape okay them will make a quick pr, btw I was meaning to talk to you. Why the excessive craterization of lightyear (importing bevy ecs and so on), I doubt that diminishes compile times. And it seens to increase the amount of code in the crate by tenfold
So the difference is that you despawn the host-client instead of simply disconnecting?
It does decrease compile times because bevy_pbr can be compiled in parallel with lightyear
no. i was a bit unclear, i disconnected before already.
the difference is that i now spawn a server at startup and keep that one spawned no matter if you host or join.
the server then just starts when you want to host and stops when you disconnect.
when starting the server i also spawn the hostclient and connect to the server.
when i stop the server, the client disconnects and then despawns.
when i join, the server stays stopped and i just spawn a new client. and when disconnecting i disconnect the client and then despawn the client.
so despawning and spawning the server again seemed to have caused the issue. idk if this was intended.
i hoped i could just have a server when needed and don't have one when just joining
You could do the same by just disabling the bevy_pbr feature non? Technically external projects would (most probably) natively import by bevys default features, while yours would just skip it
Are these your own player entities that are hanging around, or the Lightyear managed client entities? If it's the former, we use a combination of Lifetime::SessionBased and DespawnOnExit with our own ServerStates
let player = commands.spawn((
PlayerComponent,
DespawnOnExit(ServerStates::Listening),
ControlledBy {
owner: client,
lifetime: Lifetime::SessionBased,
},
Replicate::to_clients(NetworkTarget::All),
));
You could probably add a DespawnOnExit to the Lightyear client entities if they're the issue.
Spawning/Despawning the server should just work, I'm surprised it doesn't but I don't have any tests for it
Its the player entities, but i despawn everything on disconnect.
but for some reason, these observers just trigger one more time any time i connect again
pub(crate) fn handle_new_client(trigger: On<Add, LinkOf>, mut commands: Commands) {
info!("New client connected: {:?}", trigger.entity);
commands
.entity(trigger.entity)
.insert(ReplicationSender::new(
SEND_INTERVAL,
SendUpdatesMode::SinceLastAck,
false,
));
}
pub(crate) fn spawn_player(
trigger: On<Add, Connected>,
query: Query<&RemoteId, With<ClientOf>>,
mut commands: Commands,
) {
let Ok(client_id) = query.get(trigger.entity) else {
warn!("Connected client has no RemoteId; cannot spawn player");
return;
};
let client_id = client_id.0;
info!("Spawning player with id: {}", client_id);
commands.spawn((
Replicate::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::Single(client_id)),
InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(client_id)),
ControlledBy {
owner: trigger.entity,
lifetime: Default::default(),
},
PlayerId(client_id),
PlayerMarker,
Name::new(format!("Player {}", client_id)),
DespawnOnExit(IsConnected),
));
}
- at start
- first connect
- first disconnect
- second connect
and when i start a second instance of the game that just joins (only client, no server) it immediately gets 2 Netcode players
should i open an issue?
Your player entity has the following in ControlledBy:
lifetime: Default::default(),
I'm saying try the following:
lifetime: Lifetime::SessionBased
EDIT: Ah, now I see that SessionBased is actually the default, so this won't help.
Additionally, maybe your IsConnected may not be updated properly? This is what ours looks like:
fn server_stopped_observer(
_trigger: On<Add, Stopped>,
mut next_server_state: ResMut<NextState<ServerStates>>,
) {
next_server_state.set(ServerStates::Disconnected);
}
sure, but the problem is still not clear to me, sorry
@pine cape Is RemoteEntityMap not available to use? On 0.25.3 I can't export it. I need it because I want to check if an entity is already mapped on the client. Note: even though the entity has the Replicate component and is already added to its room on the server, if I send a message right after that, it fails to map that entity as it is yet to be mapped on the client
Yes if the message is received before the client spawns the entity, the mapping will fail
@unkempt sedge i released a new version
@pine cape Okey thanks btw, I made a PR to eliminate metrics the visualizer features seens to be unused can i remove it?
Docs compilke now
yes it can be removed, users can add it themselves if they want
Hi @pine cape
I think I have found an issue related to network visibility.
When many entities go from visible to not visible for a given client, it takes quite a lot of time before the server eventually stops sending "actions" to the client. (When I say "actions", I'm referring to "Recv Actions" from the DebugUIPlugin, so spawn/despawn messages).
I don't really know what those actions are though, maybe despawn actions are being queued multiple times...
I managed to reproduce the issue in the network_visibility example. To make it more obvious, I added more CircleMarkers only on the left part of the area and spawned the player on the right side (far from any circles).
When the player spawns, if I don't move the player only receive pings, no "updates", no "actions". That's the expected behavior.
When I move to the left part, all circles appear. Then I go back to the right side and all circles disappear, BUT I continue to receive many "actions" for multiple seconds.
Here is a video that demonstrates the issue: https://streamable.com/7rzdac
(I can create a github issue if needed)
ps: it's not really noticeable with just a few entities like the 4 circle markers in the initial example, here is how I have updated the init fn to spawn more:
pub(crate) fn init(mut commands: Commands) {
// spawn dots in a grid
let start = -30;
let end = 30;
for x in start..0 {
for y in start..end {
commands.spawn((
Position(Vec2::new(
x as f32 * GRID_SIZE / 8.,
y as f32 * GRID_SIZE / 8.,
)),
CircleMarker,
Replicate::to_clients(NetworkTarget::All),
// Use network visibility for interest management
NetworkVisibility::default(),
));
}
}
}
I spawned the player at: Position(Vec2::new(150., 0.)) and also updated its movement speed to 20 in shared_movement_behaviour
don't think thats an issue.
i update the connection state here:
fn spawn_server(mut commands: Commands, networking_mode: Res<NetworkMode>) {
let _server = commands
.spawn((spawn_host(&networking_mode),))
.observe(
|trigger: On<Add, Started>,
mut connection_state: ResMut<NextState<ConnectionState>>,
mut app_state: ResMut<NextState<AppState>>| {
info!("Server started, entity: {:?}", trigger.entity);
connection_state.set(ConnectionState::Connected { is_host: true });
app_state.set(AppState::Lobby);
},
)
.observe(
|trigger: On<Add, Stopped>,
mut connection_state: ResMut<NextState<ConnectionState>>,
mut app_state: ResMut<NextState<AppState>>| {
info!("Server stopped, entity: {:?}", trigger.entity);
connection_state.set(ConnectionState::Disconnected);
app_state.set(AppState::MainMenu);
},
)
.id();
}
and IsConnected is a ComputedState:
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct IsConnected;
impl ComputedStates for IsConnected {
type SourceStates = ConnectionState;
fn compute(sources: Self::SourceStates) -> Option<Self> {
match sources {
ConnectionState::Connected { .. } => Some(IsConnected),
_ => None,
}
}
}
Thanks could you open an issue?
@pine cape how can i insert a registered component on the client and have it replicated to all the others?
on the book in the Page Protocol i can see i can do:
app.register_component::<PlayerId>(ChannelDirection::ServerToClient)
.add_prediction::<PlayerId>(ComponentSyncMode::Once)
.add_interpolation::<PlayerId>(ComponentSyncMode::Once);
but i think this is outdated, as i can't find the ChannelDirection
thank you for the reply, but the component is still not getting replicated back to the server when i insert it on the client
You need to add a ReplicationSender component on the client, like in this client-to-server example: https://github.com/cBournhonesque/lightyear/blob/main/examples/client_replication/src/main.rs#L46
oooh, i see! i missed that example. thank you!
hm, its still not working
the client now has a ReplicationSender, but when i insert a component into my replicated Entity client side, the server still isnt receiving it
and the entity has Replicate? and the client-of has ReplicationReceiver?
oh, does it need Replicate on client side too? just checked and currently it only has it on server side
well yes if you replicate from client to server
nope, still not working, unless i'm still dumb and miss something (sorry if thats the case lol)
first image is hostclient view of the replicated entity (named Netcode(0)) and the ClientOf
on the server the replicated entity has Replicate
and the ClientOf has ReplicationReceiver and ReplicationSender
second image is clientview of the replicated entity. it has a Replicate and also has the PlayerIsReady Component which I insert on the client and i'm trying to have replicated back to the server (its registered and all)
third image is clientview of the client (it was too big to put it together with the second image), it got ReplicationReceiver and ReplicationSender
am i missing anything?
you're trying to replicate an entity from client 1 to server, and then from server to other clients?
the server does the initial spawn and replicates the entity to all connected clients
i then try to insert a single component from the individual clients and want that one to be replicated to the server so he can replicate it back to all clients
ah; i don't think that's possible
or rather, what you could do is:
- spawn the entity on the server and replicate to all clients
- transfer authority from server to client 1, now client 1 is responsible for simulating the entity.
- add component on client 1, which will get replicated to the server (which will replicate to other clients)
hm, or would it be easier to work with a message?
the client sends a message to the server telling he's ready, and the server inserts the component?
You could do that. But ultimately who do you want to have authority on the entity?
probably that the host has authority on everything
so thats probably how i'll do it
thank you :)
by host, you mean the server? or the client?
Because if it's the client, then you probably want authority transfer?
well, host is server and client as i do hostclient lol
You might look into using remote triggers. It is essentially a networked version of the Observer/Event system.
hmm... is there a way to do client p2p partially while using steam stuffs
Need to look into it more but it'd be nice if I could send voxel chunk updates from the authoritative client for that area and the server/host client is mainly arbitrating authority
not the biggest deal I think, but I think it just ups the upload bandwidth requirement for the host/server
you mean send from client 1 to client 2 directly?
it's not really something I explored too much. Theoretically you could as long as you create a Link between the 2
and ya, this
I encountered an issue when building a project with Lightyear.
When I start the server, movement between PC client and PC client works fine with no issues. However, with the WASM web client, when I press the movement keys, there's a very long delay before the server and the other PC client receive the message and show the movement.
client:
fn startup(
mut commands: Commands,
mirlegend_settings:Res<MirlegendSettings>,
mut next_screen_state: ResMut<NextState<Screen>>,
) -> Result {
let auth = Authentication::Manual {
server_addr: SocketAddr::new(IpAddr::V4(mirlegend_settings.shared.server_addr),mirlegend_settings.shared.server_port),
client_id: rand::random(),
private_key: Key::from(mirlegend_settings.shared.private_key),
protocol_id: mirlegend_settings.shared.protocol_id,
};
let netcode_config = NetcodeConfig {
token_expire_secs: 300,
..Default::default()
};
let certificate_digest = include_str!("../../../../certificates/digest.txt").to_string();
let conditioner = LinkConditionerConfig::average_condition();
let client = commands
.spawn((
Client::default(),
LocalAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0)),
PeerAddr(SocketAddr::new(IpAddr::V4(mirlegend_settings.shared.server_addr),mirlegend_settings.shared.server_port)),
Link::new(Some(RecvLinkConditioner::new(conditioner.clone())),),
ReplicationReceiver::default(),
PredictionManager::default(),
NetcodeClient::new(auth, netcode_config)?,
WebTransportClientIo { certificate_digest },
Name::from("Client"),
))
.id();
commands.trigger(Connect {
entity: client,
});
next_screen_state.set(Screen::InConnect);
Ok(())
}```
server:
use core::net::{IpAddr, SocketAddr};
use mirofme_common::settings::MirlegendSettings;
use lightyear::prelude::server::*;
use lightyear::prelude::*;
use bevy::tasks::IoTaskPool;
use async_compat::Compat;
pub(super) struct ServerConnectBasePlugin;
impl Plugin for ServerConnectBasePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, startup);
}
}
/// Start the server
fn startup(mut commands: Commands, mirlegend_settings:Res<MirlegendSettings>) -> Result {
let netcode_config = NetcodeConfig {
protocol_id: mirlegend_settings.shared.protocol_id,
private_key: mirlegend_settings.shared.private_key,
..Default::default()
};
let identity = IoTaskPool::get()
.scope(|s| {
s.spawn(Compat::new(async {
Identity::load_pemfiles(("certificates/cert.pem").to_string(), ("certificates/key.pem").to_string())
.await
.unwrap()
}));
})
.pop()
.unwrap();
let server = commands
.spawn((
NetcodeServer::new(netcode_config),
LocalAddr(SocketAddr::new(IpAddr::V4(mirlegend_settings.shared.server_addr),mirlegend_settings.shared.server_port)),
WebTransportServerIo {
certificate: identity,
},
Name::from("Server")
))
.id();
commands.trigger(Start {
entity: server,
});
// 启动相机以及实体检测器
commands.spawn(Camera2d);
Ok(())
}```
@pine cape Opened 4 quickie qol prs, I think a friend of mine will open a new one fixing the ci completely. When he does so would you kindly release again? I really want that entitymap e.e
You can import the entity mapping directly from lightyear_serde also
@pine cape if you dont slash, lightyear into portions I cannot (lightyear replication and so on) you cant.
do you also get this with the examples?
I can run the official simple_box example smoothly. It might be that I don't have a deep enough understanding of Lightyear - I thought it was an issue with my network connection code, but I found that it wasn't. I'll carefully look for issues in other areas myself
I've noticed some oddities with WebTransport on web too, but I haven't dug into it yet. Just called it "good enough" and moved on for now.
2025-11-18T13:33:03.753588Z WARN bevy_enhanced_input::action::fns: action MoveInput (607v0) expects Axis2D, but got Bool`` @pine cape Ahmm do you know what this is about? I have no idea on what causes this warning just wondering what system I should look at
It's because of how BEI integration is setup, unless it's spammed all the time you can just ignore it
well damm, it spawned all the time T.T
i mean it spawned for a while but not continously i dont see any rollbacks and so on
@pine cape thanks for merging my PR so quickly 🙂 going to have a few more on the way making timline syncing more robust overall. I agree doing this in PostUpdate isn't ideal, and I also agree with the comment (about sync_timelines) that says we could run this right after RunFixedMainLoop - applies to sync_from_local_timeline as well. But I wanted to keep this PR scoped to just the bug fix
Really appreciated, as the whole sync logic could use more eyes!
Yeah, I think something like a kalman filter could make the RemoteTimeline recover much faster from server side lag spikes and stuff like that
just gotta find the right balance between bulletproofness and simplicity. obviously this isn't a fighter jet, we don't need to be spending more than, like, 100 cycles on clock sync
Hi,
Shouldn't
app.add_systems(PreUpdate, Self::send.in_set(LinkSystems::Send));
app.add_systems(PostUpdate, Self::send.in_set(LinkSystems::Send));
instead ? To match the LinkSystems:Send Schedule here: https://github.com/cBournhonesque/lightyear/blob/a4e87d6a2461d10747b1911f4a7fa23ce2dda5a0/lightyear_link/src/lib.rs#L339
Yes!
I know of them but that's about it
@pine cape would it be fine to add a simple typos CI check?
@pine cape Also curiosity, I know lightyear does position to transform but never actually got why, it does not seem to be a source of indeterminism. Would you mind telling me the reasons why? Comments seem little disperse, btw I ask because I would like to avoid systems like this
fn receive_server_teleports(
mut message_reader: Single<&mut MessageReceiver<TeleportPredicted>>,
position: Query<Has<Position>>,
mut commands: Commands,
) {
for message in message_reader.receive() {
if message.target == Entity::PLACEHOLDER {
info!(
"Player on boot up is teleported on server, but the entity does not exist on client. Note - TODO lightyear only send message if mapped"
);
return;
}
debug!("Teleported {} to {}", message.target, message.destination);
let has_position = position.get(message.target).unwrap_or_default();
// Due to lightyears weird position to transform necessity
if has_position {
commands
.entity(message.target)
.insert(Position(message.destination));
} else {
Transform::from_translation(message.destination);
}
}
}```
upd. disregard, I might have found a bug in my manual id mapping logic
I'm not sure how this happens, but some replicated entities randomly become children of other entities... absolutely no idea why though, as those entities aren't related in any way. My only guess is that lightyear mistakenly assigns some entities as children of others
Is that actually necessary? Lightyear has multiple transform/position sync options now, idk if you've experimented with them
hello!
I struggle to get bevy_enhanced_input to work with lightyear. i tried following the example and got it somewhat working.
when my client connects to the server (hostclient) i sometimes get this error:
2025-11-19T20:10:15.055318Z ERROR lightyear_inputs::client: received input message for unrecognized entity entity=PLACEHOLDER target_data.states=BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [] } end_tick=Tick(603)
sometimes i get a lot of them, other times just once or twice, but only at the beginning. this is concern 1
concern 2, currently the client that joins gets for both clients (client and hostclient) the inputs replicated, but the hostclient only gets his own input replicated. and i don't know if it is a problem or not, and how to fix it.
concern 3 is that my movement code:
fn movement_host(
trigger: On<Fire<Movement>>,
mut position_query: Query<&mut Position, Without<Predicted>>,
) {
println!("movement_host1");
if let Ok(position) = position_query.get_mut(trigger.context) {
println!("movement_host2");
shared_movement_behaviour(position, trigger.value);
}
}
only gets triggered on the hostclient movement itself, but not when the client moves. so currently the client only moves on his end and then gets moved back to where the server thinks he is.
i guess second and third concern have todo with each other.
any idea what i might have missed?
Sure
Maybe some entity mapping issue?
Yes yes, that's what I mentioned in the upd part :) apologies for the disturbance
you can choose how to replicate avian components; there are some comments explaining the logic in lightyear_avian. I still think there's something wrong with replicating hierarchies, but I think that might get fixed in the next avian release
concern 1. That's normal, it's because the server can receive the InputMessage before the BEI entities have been replicated. It should only happen at the beginning and can be ignored.
concern 2. So the host-client is not getting the inputs of the other clients? It is working in the bevy_enhanced_input example, so maybe try to see how your code differs from that?
for the life of me i can't seem to figure it out.
i checked side by side the example with mine and i don't find the issue.
the hostclient does not get the inputs replicated, and i don't know why.
What version of Lightyear are you running?
the latest one, 0.25.5?
i'll do a new empty project now and just 1 to 1 follow the example, check if it works, and then slowly re-add what i had...
@pine cape I know but in my opinion there should be only one king (Transform), I dont really see why for the other ones as avian natively handles position and rotation. Also issues such as child inheriatance can be quite a pain
how do you know the inputs are not replicated? can you confirm that the inputs are replicated in the example?
then just use AvianMode::Transform
I have rollback on that one 🙁
Guess I will try to see the why now tho
maybe the system order for AvianMode::Transform is not correct; i haven't tested it in a rollback scenario
@jade ember I had the same issue as you and this is how I solved it. For some reason compiling with both the server and client features enabled causes it.
I think so to. I think i read that avian itself wants to switch to Transform internally if it manages to solve some problems. AvianMode:: Transform works on my side.
i don't think thats the problem, the example works fine with both features enabled
I have both server and client features enabled and hostclient inputs are replicated, although they seem to be more unreliable than client rebroadcasted inputs which is something I've been debugging myself.
I think avian wants to switch to a PhysicsTransform2D (for 2d) that doesn't include scale
I'm currently debugging why only the parent entity is replicating while setting up networking for my project, and I just wanted to flag that the docs for DisableReplicateHierarchy seem outdated. It talks about both disabling and enabling replication, and it refers to a component ReplicateLikeParent that doesn't exist. Based on the name my guess is that most of the second paragraph is wrong?
ReplicateLikeParent is now ReplicateLike
Thanks! I still think the wording "If the component is added on an entity with Replicate, it's children will be replicated" seems wrong? Isn't it doing the opposite, namely stopping all replication from that point onwards?
Yep that's incorrect, I'll update
Hi i'm trying to implement a fps camera using mouse motion with bevy enhanced input but when i add Input delay, player camera moves after the delay. Is this normal behaviour ? Do i need to remove input delay ?
You should be able to follow roughly what we did using an ActionMock to set up a "client authoritative" camera.
#1189344685546811564 message
I think you might be able to do it with a Vec2 instead like the FPS example does with Leafwing's .set_axis_pair. I think the Projectiles example does this?
I think that works, I could also add a component that marks an input as Local, which would remove input delay + not replicate.
Or do you need the player's orientation to be replicated to the server?
hey guys! i was experimenting with WebTransport and saw that some of the lightyear devs raised this issue about disconnects when switching tabs: https://github.com/w3c/webtransport/issues/600
i am facing the exact same issue with bevy_renet2, and was curious how you guys solved this (if at all)?
We use the bevy_web_keepalive plugin that keeps the event loop active even when bevy is in the background tab
When i connect with other client, first client transform not synced up unless first client moves
@unique plover i need help about bevy_tnua integration
do you use
app.add_plugins(lightyear::avian3d::plugin::LightyearAvianPlugin {
replication_mode: AvianReplicationMode::Transform,
..default()
});
app.add_plugins(
PhysicsPlugins::default()
.build()
// disable syncing position<>transform as it is handled by lightyear_avian
.disable::<PhysicsTransformPlugin>()
.disable::<IslandSleepingPlugin>()
.disable::<PhysicsInterpolationPlugin>(),
);
This ?, somehow my positions get desynched
If you don't go the full prediction route like we discussed, there was a hack that I used to do that can make Interpolated remote players kinda work, although you will certainly get desync on player-to-player collisions:
#1189344685546811564 message
That was on older lightyear when the confirmed entity was separate. You'll have to adapt that to the Confirmed<...> components.
@pine cape @unique plover i found the issue.
i have a server spawned on both sides (hostclient and normal client).
the host has it started, while the client does not.
the client now doesn't replicate its inputs to the host.
if i only spawn a server on the hostclient, it now works.
i assume this is a bug?
Ah, yeah we don't spawn the server on the host until you select host from the main menu. We spawn the host's client via an observer when it starts listening. Clients that want to join the host just spawn a client when connecting from the main menu.
Both server and client plugins are present on all clients though.
the reason why i have a server any time is because i ran into this issue if i didnt
but i'll look into it again, maybe i made a mistake back then
update: yes, have made a mistake back then -_-
anyways, i would have expected it to work the way i've done it. or is this to be expected?
i'll open an issue for this otherwise
Ah great find, I can push a fix. It should be simple
2025-11-25T18:37:40.042449Z WARN bevy_ecs::error::handler: Encountered an error in command <bevy_ecs::system::commands::entity_command::remove<psycho_player::shared::state_machine::Interacting>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}: The entity with ID 505v64 does not exist (enable track_location` feature for more details)
If you were attempting to apply a command to this entity,
and want to handle this error gracefully, consider using `EntityCommands::queue_handled` or `queue_silenced`.`
@pine cape Just curious have you encountered a warn such as this one, it occurs everytime I disconnect my client and it is entities get despawned with him
@pine cape also you are in our ui
you probably have a system that tries to despawn the entity while lightyear wants to despawn it at the same time.
enable the track_location feature to see which one despawns it.
okay thanks now you go into the ui
You can use try_despawn instead of despawn
@jade ember does my latest PR fix your issue?
i'll have to check tomorrow
i changed it now anyways so that i only spawn the server when needed, so it's not an issue currently for me
How can i log how many kb/s transmitted or received by both client and server ?
You can add this plugin: https://github.com/cBournhonesque/lightyear/blob/36b21c512f95bd36a75ce3a64c7a5910bba13232/lightyear_ui/src/debug.rs#L498-L498 to have a view with some debug info
A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear
I added it but it doesn't show anything
Try enabling the metrics feature
how would i build a chat system into lightyear? doesn't look like InputAction can support strings
Chat shouldn't be an input as it doesn't need to be tick-synced. You can just use create a custom Message to store your string and send it to other peers
is there any example where i can see how i can configure lightyear and avian to work with transform instead of position?
It should just be using AvianReplicationMode::Transform
the client that joins crashes now as soon as the player lands on the ground and i get this error:
thread 'main' panicked at C:\Users\zwaze\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\avian3d-0.4.1\src\dynamics\solver\islands\mod.rs:522:9:
assertion failed: contact.island.is_none()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `avian3d::collision::narrow_phase::update_narrow_phase<avian3d::collision::collider::parry::Collider, ()>`!
Encountered a panic in system `avian3d::schedule::run_physics_schedule`!
Encountered a panic in system `bevy_app::main_schedule::FixedMain::run_fixed_main`!
Encountered a panic in system `bevy_time::fixed::run_fixed_main_schedule`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
which is why i was originally asking, maybe i'm missing any replication registrations and so on, but in the test i don't see really anything that differs from what i have
yeah the PhysicsTransformPlugin and the PhysicsInterpolationPlugin
only when i use transform sync?
because in the avian 3d characters example it is commented out and not disabled
It causes issues with rollbacks, the avian 3d characters probably works because there is no rollback? Idk
its also not disabled in the avian_physics and the fps examples lol
but thanks, i'll try that out
@pine cape should/can i still do this when switching to AvianReplicationMode::Transform?
app.register_component::<LinearVelocity>()
.add_prediction()
.add_should_rollback(linear_velocity_should_rollback);
app.register_component::<AngularVelocity>()
.add_prediction()
.add_should_rollback(angular_velocity_should_rollback);
Probably yes, otherwise you will be mispredicting on the client
i disabled the IslandPlugin but now i get this error
thread 'Compute Task Pool (8)' panicked at C:\Users\zwaze\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\avian3d-0.4.1\src\dynamics\solver\plugin.rs:398:51:
index out of bounds: the len is 1 but the index is 1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `avian3d::dynamics::solver::plugin::prepare_contact_constraints`!
Encountered a panic in system `avian3d::schedule::run_physics_schedule`!
Encountered a panic in system `bevy_app::main_schedule::FixedMain::run_fixed_main`!
Encountered a panic in system `lightyear_prediction::rollback::run_rollback`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
hm, it only crashes once i do something, any kind of input...
nvmd, disabled all the movement systems and it still crashes
How we can transfer authority right now ?
and also what is this error means:
2025-11-28T12:09:28.425177Z ERROR lightyear_replication::send::plugin: Error buffering ActionsMessage: Serialization(InvalidValue)
I'm trying to spawn 9 server controlled entities with prediction
commands.spawn((
ZombieBundle::new(variant, position),
Replicate::to_clients(NetworkTarget::All),
InterpolationTarget::to_clients(NetworkTarget::All),
PredictionTarget::to_clients(NetworkTarget::All)
));
and also is there a way to not rollback rotation ? make it only client authoritive ?
worth mentioning, only the client crashes
i have some support for that but it's not polished/tested. What are you trying to do?
Hm idk, looks like something very wrong happened. Are you using main or the latest released version
@pure drum @unique plover the issue with the action `EvaMoveAction` (`427v0`) expects `Axis2D`, but got `Bool`
errors seems to be that the client is started a bit later than the server, but no more than 10 ticks before.
Because of that the hard tick sync does not kick in (I only snap the client tick to the correct tick if it is more than 10 ticks away from the ideal tick). Instead the ticks are adjusted by changing the speed of the client timeline, but it takes time to adjust
I can push a fix to always do a hard tick sync the first time we are trying to sync
latest
I just want to give authority to player of player controlled entity
This should fix the input sync issues you've been observing: https://github.com/cBournhonesque/lightyear/pull/1333
Interesting.. pointing to main now and it looks like the client is in sync (if im reading it correctly), but i still get the Axis2D vs Bool warning. On my setup is happens 100% of the time now.
2025-11-29T19:25:17.053510Z TRACE lightyear_inputs::server: received input message tick=Tick(16) client_id=RemoteId(Netcode(13633984869232431464)) action=EvaContext message.end_tick=Tick(16) message.inputs=[PerTargetData { target: Entity(425v0), states: BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } }]
2025-11-29T19:25:17.053520Z DEBUG lightyear_inputs::server: Rebroadcast input message InputMessage { interpolation_delay: None, end_tick: Tick(16), inputs: [PerTargetData { target: Entity(425v0), states: BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] } }], rebroadcast: false } from client RemoteId(Netcode(13633984869232431464)) with rebroadcaster None action=BEIStateSequence<EvaContext>
2025-11-29T19:25:17.053530Z TRACE lightyear_inputs::server: Updating InputBuffer: InputBuffer<DebugName { name: "lightyear_inputs_bei::input_message::ActionsSnapshot" }>:
Tick(16): ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }
using: BEIStateSequence { start_state: ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }, diffs: [SameAsPrecedent, SameAsPrecedent, SameAsPrecedent, SameAsPrecedent] }
2025-11-29T19:25:17.053550Z TRACE lightyear_inputs::input_message: input buffer after update: InputBuffer { start_tick: Some(Tick(16)), buffer: [Input(ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) })], last_remote_tick: Some(Tick(16)) }
2025-11-29T19:25:17.053866Z TRACE lightyear_inputs::server: input buffer on server tick=Tick(17) server=419v0 input_buffer=Mut(InputBuffer { start_tick: Some(Tick(16)), buffer: [Input(ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) })], last_remote_tick: Some(Tick(16)) })
2025-11-29T19:25:17.053895Z WARN bevy_enhanced_input::action::fns: action `EvaMoveAction` (`425v0`) expects `Axis2D`, but got `Bool`
Hm the client is still behind the server...
Oh 😐
(just making 100% sure im actually using the latest main)
yup looks like it
if you need to look at my code, i pushed the dep change to main
Fixed authority handling and updated the distributed_authority example: https://github.com/cBournhonesque/lightyear/tree/main/examples/distributed_authority
It should now be fairly straightforward to transfer authority over an entity
I'm trying to create a Host in separate mode (2 bevy apps I think, since multi-world is not a thing). Couldn't find any examples on that. Any ideas?
Someone mentioned crossbeam I believe
I think you can find one here: https://github.com/SueHeir/lightyear-menu/tree/main/src%2Fnetworking
But yeah the idea is to create two apps connected by crossbeam
How can i get same elapsed_time for server and client ?
or something similar to that
trying to do this:
let current_tick = timeline.now().tick();
if current_time - weapon.last_shot_time < stats.fire_rate {
info!("Fire rate not met");
return Ok(());
}
I figured
Yep I would base it on ticks
Would it be possible to make NetworkVisibility::is_visible public? Having this exposed would be useful for implementing a rate-limited visibility gain/lose system.
Sure, can you PR it?
Hi, I guess my issue is related to this issue https://github.com/cBournhonesque/lightyear/issues/1336 but when working with only interpolation and so, no input delay, many inputs are never processed by the server (because they are from past I guess), adding input delay fix the issue but.. add delay ^^
Using only native inputs btw if that can help
and you have no artificial lag added?
things are known to not work properly with 0 artifical lag added
I have the issue with or without LinkConditionerConfig
I managed to replicate the issue by updating the network_visibility example, don't have it anymore.. but tldr is only use interpolation InterpolationTarget::to_clients(NetworkTarget::All) and no PredictionTarget for the player, remove the movement system from the client (only update player from server) run the example and try to move, you will see that player does not move or never stop moving when it start to move
I did my test with UDP transport
Here is a video, not ez to show... https://streamable.com/uce5o1
This is using this version of the example: https://github.com/OlivierCoue/lightyear/commit/927bbdcfa4931683c3a9d38195f0f561ac9695c7
And you'r right that with more ping/lag its less common, but here I use:
let conditioner = LinkConditionerConfig {
incoming_latency: Duration::from_millis(20),
incoming_jitter: Duration::from_millis(5),
incoming_loss: 0.00,
};
Which is standard latency with fiber/close server (I believe so) and it appears a lot
It's possible that the sync logic changes have introduced some bugs, i will take a look
I'm thinking of changing some of the replication internals.
Things i'm unhappy with:
- the current design allows for a lot of flexibility since any entity can be a Link, and you could have multiple Servers in the same app. However this adds a lot of complexity for not much payoff (i don't think anyone is using multiple servers in the same app anytime soon)
- even if multiple-servers were a thing, what we would want is multiple "server types": i.e. we could have a SteamServer + UdpServer + WebTransportServer in the same app, each tracking their own sets of clients, but the App as a whole runs with the role of a server, with a single global timeline
- some things are penalized because of this flexibility. For example even if you run lightyear as a client with lient to server replication, we have to use HashMaps to keep track of which ReplicationSender your entity is replicated on, even though we should statically know that there can only be one ReplicationSender since you're running in client mode
I'm thinking of introducing a global static LIGHTYEAR_MODE with 4 settings: (or it can be implemented as feature flags)
- Server: you can have multiple connections in your app, but there is one 'logical' server. This also includes HostServer.
- Client: you will only have a single connection in your app
- P2P: usually used with input replication only
- Flexible: current config where anything goes. You could have 2 "client" links each connected to 2 different servers, each replicating a subset of the world, etc.
Maybe this setting is not necessary since the optimizations are mostly relevant on the send-side of replication, and in this mode you would still only have a single ReplicationSender
Then the data structures used could be different based on the global MODE of the app
Hm this probably would not work if you want to spawn multiple apps (Client and Server) in the same process..
Maybe the mode can just be stored in a resource
Since you're considering moving away from multiple servers and plan to rework the internals, maybe consider using bevy_replicon under the hood?
Sorry - I know I've already asked a few times, but many things have changed since then. We've implemented features that were missing back then, such as prioritization, grouping, etc. I think the only major piece we're still missing is proper delta compression.
The advantage is that you'd get very robust and efficient replication, along with simplified maintenance. You'd also gain a few nice features, such as per-component visibility, serialization specialization, and archetype-based replication rules (for example, you can define (Health, Player) to replicate only when both are present on an entity). I also remember that your messages/events aren't tied to ticks? We have this too: so if the client received a message too early, it will be buffered. This behavior is opt-out per-event.
The downside, I think, is that this would require more rework on your side than you're currently planning. So feel free to say "no" - that's totally okay!
But I feel like a more united ecosystem for networking in Bevy would be awesome.
Hm I would need more time to evaluate what's missing from replicon or what could be adapted, but I would be down to create a new crate lightyear_replicon that uses replicon (that would replace lightyear_replication). I would probably be keeping lightyear_replication active so that I can experiment with new replication features.
I think the main replication logic is similar between lightyear and replicon:
- full consistency within one entity
- distinction between actions (spawn/despawn/insertion/removal) and updates
- actions are reliable
- a received update is only processed after all actions prior to that update have been processed
Some of the big things that come to mind:
- i definitely want client to server replication. The usecase would be to have a world where the simulation load is distributed across client. I have a authority mechanism where you can specify which peer has authority over an entity
- you support prespawning using Signature, right?
- I have some special-case serialization for predicted/interpolated entities. A component is inserted/deserialized differently on a Predicted/Interpolated entity. I'm not sure how that could be supported in replicon?
- How does grouping work? My main usecase is that I want all replication messages for all predicted entities to be sent in a single message.
- I would like to support P2P settings where a client can be connected to multiple other clients
Also I some other questions:
- you mention having message/event tied to ticks. Is this done simply by guaranteeing that a message sent by a client at tick T is received on the server at tick T? This works only for client to server messages, no? (since the client timeline is ahead of the server)
Yes, I think it's reasonable!
I think the main replication logic is similar between lightyear and replicon:
Yes, I think so.
i definitely want client to server replication
Ah, right, that's another thing that's missing. I can add it if it's important to you.
It's just quite niche - VR is the only real use case I know, and it comes with security caveats.
you support prespawning using Signature, right?
Yep!
I have some special-case serialization for predicted/interpolated entities. A component is inserted/deserialized differently on a Predicted/Interpolated entity. I'm not sure how that could be supported in replicon?
We have a special client markers API to override how a component is written. So you define a marker for predicted and interpolated entities and override how the component is written (for example, "write into component history").
It's separate from deserialization, so users can still have custom deserialization and use it with the markers API.
How does grouping work?
Via relationships. You mark the desired relations as grouping, and internally we build a graph of related entities. We guarantee their changes will be sent in a single message.
I would like to support P2P settings where a client can be connected to multiple other clients
You can have connection, we just provide events and replication. Or you mean that you want clients talk to each other via events?
you mention having message/event tied to ticks
It works for server-to-client messages and is not related to the client timeline. Imagine the server sent replication and a message on tick X. The client didn't receive the replication for tick X, only the message. If tick X contained archetypal changes (such as insertion, removal, or spawn), we buffer the message until the client receives replication for a tick ≥ X.
For example, imagine the server spawned a door and sent a command that references it. If we apply message too early, it will result in logical error.
So you define a marker for predicted and interpolated entities and override how the component is written (for example, "write into component history").
But how can I guarantee that the Predicted component is added first, before everything else?
I guarantee that right now by adding predicted/interpolated in the spawn message: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_replication/src/message.rs#L75
Ah, right, that's another thing that's missing. I can add it if it's important to you.
It's just quite niche - VR is the only real use case I know, and it comes with security caveats.
I don't think it's only VR. Any bigger open-world game could want the client to participate in the simulation.
How does grouping work?
Via relationships. You mark the desired relations as grouping, and internally we build a graph of related entities. We guarantee their changes will be sent in a single message.
Hm maybe i could work with that. That means I would need to create a 'Prediction' relationship so that all entities that need to be predicted are guaranteed to be one the same group.
I would like to support P2P settings where a client can be connected to multiple other clients
You can have connection, we just provide events and replication. Or you mean that you want clients talk to each other via events?
I just mean that replicon's architecture is very oriented towards 'client-server'.
I think the replicon codebase is very clean, but one thing i really like in lightyear is having separate entities for each link between 2 clients, and being able to customize that link with components likeReplicationSender,ReplicationReceiver.
How would i adapt this with replicon? I'm not too familiar with replicon internals, do you have a Server resource and a Client resource?
But how can I guarantee that the Predicted component is added first, before everything else?
I think clients know which entities they predict and which they interpolate, is this correct?
So they receive an entity with its initial values first, then mark it asPredictedorInterpolated. All subsequent values will be written differently based on what the client wants.
At least I think that's how @fiery gazelle integratedbevy_rewind. Please correct me if I'm wrong.
Any bigger open-world game could want the client to participate in the simulation.
This is the first time I'm hearing about something like this 🤔 Do you know of any game that does that?
I know it could be used for VR, just to avoid possible rollbacks, since those could cause nausea. At least that's what Joy mentioned when we had a discussion about it.
That means I would need to create a 'Prediction' relationship so that all entities that need to be predicted are guaranteed to be one the same group.
Yep! But why do you need all predicted entities to be in the same group? Is it for lockstep?
I just mean that replicon's architecture is very oriented towards 'client-server'.
Yes, right now we have theClientMessagesandServerMessagesresources. They are used to read and write messages by backends.
And we haveClientStateandServerState, which are Bevy states toggled by the backends. So ifServerStateis set toActive, the server starts replication.
If the client becomesClientState::Connected, it starts processing replication.
I can rearchitect this to use entities, but Bevy states are global... And they are very convenient, you get OnEnter/OnExit schedules, in_state conditions and things like StateScoped(ClientState::Connected) (I think it's called differently in 0.17).
Maybe we can have a switch between P2P and client–server and have different states?
I went with client–server because replication in P2P is quite niche (basically, it's for VR). And deterministic replication is already done by GGRS, so I didn't bother 😅
But I'd prefer to foucs client-server for now to polish it and extend it later. It's what most people want to use anyway.
On the point you mentioned me for: Correct that's how I approach it. I get components from the server, then have Predicted as a required component there
Why not both entities and states? This is effectively what I'm doing with our usage of Lightyear. You can keep them in sync with observers like we do or I think you could even use a computed state.
I will say that I prefer APIs to be as entity-ified as possible.
I mean with P2P you don't have ServerState::Active. You will have something like a ReplicationSender component
Yeah, but you could still have ServerState when not using P2P, no? I find the component to be useful to fire observers off of and the States convenient to scope systems.
There is no concept of server in P2P.
We don't have a server resource in Replicon. We just have message buffers which are resources that always present. And the activation happens via states that are global.
It's possible to make buffers components and activation via components as well, but states for P2P will be different.
So they receive an entity with its initial values first, then mark it as Predicted or Interpolated. All subsequent values will be written differently based on what the client wants.
At least I think that's how @fiery gazelle integrated bevy_rewind. Please correct me if I'm wrong.
In lightyear the sender (server) usually adds Predicted/Interpolated to the entity. I found it to be more ergonomic in most cases, since the server knows which client owns the entity, i.e which client should be predicting vs interpolating.
(the client can also add it)
Any bigger open-world game could want the client to participate in the simulation.
This is the first time I'm hearing about something like this 🤔 Do you know of any game that does that?
Take a look at https://docs.coherence.io/manual/authority or https://doc.photonengine.com/fusion/current/manual/network-topologies#shared-authority
Yep! But why do you need all predicted entities to be in the same group? Is it for lockstep?
It's not really for lockstep, I'm just not sure how to make prediction work otherwise.
@fiery gazelle how do you rollback if multiple predicted entities are at different ticks? You just rollback to the earliest of these ticks?
I guess the main risk is that this could cause a wrong re-prediction and more rollbacks, but maybe it would still work?
Fusion supports three network topologies: Within Fusion network topologies are called 'Modes'. The server application is built from the Unity project
Maybe I'm not being clear? I understand P2P doesn't have a server, but when you do have a server could ServerState not exist then?
Maybe you're focusing on internal states for Replicon to use and not end-user states? To be completely clear, P2PStates should probably also be exposed.
I'm assuming here that we're gonna have to specify a mode when we create our connection. Either by spawning entities or setting some higher level state to signify Client-Server or P2P.
Conditional compilation with features is also a really good option here to select our "mode" and expose the correct states. I can't immediately imagine a use case that includes both P2P and Client-Server architectures in the same app?
To the oldest tick that changed, then load new data every tick for the entities that have it, coninuing on predictions for the rest
In lightyear the sender (server) usually adds Predicted/Interpolated to the entity.
In this case, you could probably include a message that instructs the client to add predicted/interpolated values.
During the first replication, the client will receive the initial values as usual, so I think that should be fine.
Take a look
Thanks for sharing!
The first library mentions that shared authority is just easier to use as a reason, which I don’t really like. I hate when devs ignore security in the beginning and fix it later.
But Photon's explanation makes sense to me. When cheating doesn't affect others, mobile devices and the web with limited perfomance and bad connection 🤔
So not as niche as I though.
Bevy states I mentioned aren't internal. It's user API. It's managed by backends, but users react on it. Yeah, I can remove them, but Bevy states are very convenient.
Yep, that's what I was thinking.
But P2P probably won't be a priority for me in the near future (unless someone volunteer). I think it would be more reasonable to focus on client-server for now, there are still things I need to improve or implement.
Like the authority support you mentioned.
I'm saying keep the states in the public API and use entities as well. Set the states using observers on the entities or use a computed state. If the app is in P2P mode don't set ServerStates (or better yet don't compile it at all if the P2P feature is used) (when/if P2P is added)
Ah, in this case, that's exactly what I meant 🙂
The first library mentions that shared authority is just easier to use as a reason...
Cooperative games are another important use case to consider. You don't necessarily always need to have strict server authority with them. Client authority can absolutely speed up the development process and may be a good choice.
You'll get higher latency. Instead of server->clients you get client->server->clients.
I'd go server authority unless I absolutely need to avoid rollbacks.
This is the core of what Periwink was arguing though. In the case that cooperative players in an open world setting you don't care about latency from other clients when they're far apart. You want the authority to simulate things near to you so you don't have latency at all.
It seems like you're really focusing on PVP networking, which is fine, but that's the core of why there are currently multiple Bevy networking crates. There are differing use cases.
IMO Lightyear offers the most flexibility at the cost of a (slightly) more complex API. (Although recent advancements like Confirmed<SomeComponent> have really improved that end-user complexity)
Why hasn't Replicon considered using Lightyear as a base instead since you would be considering rearchitecting to entities anyway?
To be completely clear, my own use-case doesn't use client authority despite being a cooperative open-world. I do appreciate the ability to choose when to incorporate client authority when I want though.
In our own game the replicated buildable previews could very easily be client authority instead, and I may end up switching to that style if it makes more sense architecturally.
In that case latency doesn't matter at all, because the previews are only a visual indicator.
If you predict things, you won't experience the latency.
Shared authority is not about avoiding latency, it's about avoiding rollbacks. For example, VR. Proton library also mentions low-powered devices (to save performance) and bad connections (where rollbacks will be too noticable).
If you predict then you have to roll back. The CPU cost may not be worth it in certain games. I'm still on the fence if I want it in ours. We will have a very heavy CPU demand from gameplay systems in general.
I feel like you writing like you disagree with me, but you saing exactly what I said right now 🤔
I.e. one of the use cases is to for better performance, that I wrote above.
I think the mismatch in understanding is that in Lightyear you don't have to have all-or-nothing with server authority. Some can be Client Authority if you want. You can mix-and-match. I might end up making certain systems predicted and others client authority.
I understand how authority works, but I'm not sure how it contradicts what I say 🤔
As for the focus, I don't think I focused on PvP. I didn't implement authority because it's kind of niche, so I focused on client-server. Not as niche as I thought (i.e., it's only VR), but still not used very often.
But as I said, I'm not against implementing it. But it needs to be carefully designed and explicitly documented, otherwise people might use it for wrong reasons.
Shared authority is not about avoiding latency, it's about avoiding rollbacks
This is what is confusing me
I didn't implement authority because it's kind of niche
Cooperative games in general?
To be clear, I do agree with you that it's a performance choice. And cooperative games can make that performance choice without being on a lower-end platform or VR.
Client authority is useful when you want to avoid rollbacks, such as in the mentioned VR case or when you want to reduce CPU load.
It's not really related to cooperative games. You can simply use prediction.
I think there's a bit of a language barrier here.
Could be, I'm not a native speaker 😢
Client authority comes with 2 disadvantages:
- Cheating.
- Latency for other players.
For coop first point doesn't matter. But the second one still counts.
If you really need to reduce the load, client authority makes sense. This is why I'm saying it's about avoiding rollbacks.
The articles Peri linked explains this nicely.
Are you trying to instruct me on how client authority works or are you arguing why client authority is niche?
I am arguing it isn't niche since coop exists.
I'm not trying to instruct at all.
The latter. I feel like you’re saying it’s a common choice for cooperative games, but I don’t necessarily agree with this 🤔
I feel like the choice comes down to performance. If you can use client-side prediction, it's better to use it.
As for why I didn't use Lightyear as a base: I wanted to develop only the replication library I needed for my game.
You might ask why I didn't just use Lightyear, since it provides replication.
It's simple: Lightyear didn't exist back then.
Now you might ask why I haven't abandoned my library.
It's a fair question, given that Lightyear implements things like the mentioned delta compression and shared authority.
However, I think my library is more optimized based on what I saw (and I put a lot of effort into making things optimal) and more robust (simply because my scope is smaller and the library is older - you're unlikely to face a replication bug). And we also have a few other interesting features.
But! Integrating for Lightyear wouldn't be easy and would require losing the mentioned features (at least until I reimplement them, which would take time). That's why I'm explicitly saying it's fine to say no - I just wanted to make sure it was considered, because there are some advantages if we go this way. So there's no pressure, and @pine cape - if along the way you decide it's a bad idea, that's also totally okay.
Apologies for blowing up the channel y'all!
Conversations like these give me faith in the Bevy ecosystem, it was quite informative as well
the current design allows for a lot of flexibility since any entity can be a Link, and you could have multiple Servers in the same app. However this adds a lot of complexity for not much payoff (i don't think anyone is using multiple servers in the same app anytime soon),
Having multiple servers reminds me of SpacetimeDB, I think because it's mostly relevant when it comes to MMO.
One potential use case of multiple servers could be host-migrations, but that would be a temporary "second" server. Maybe not the best way to do that
Perhaps even server migrations if the dedicated server goes down for some reason (e.g. spot interruption)? Just throwing ideas out there
Yeah this is an interesting use-case that I hadn't considered. Another possibility is some kind of high-tenancy dedicated server. You would host many instances of a competitive server on a single running instance of the game binary. You would benefit from shared memory for things like levels (useful when the level layout is the same for many instances e.g. Rocket League or similar) at the cost of potentially taking many instances offline if there's some kind of crash.
You would also benefit by having reduced connections to your backend services.
+1 for high-tenancy dedicated server. Reduces the need for using an orchestrator like Kubernetes to spin up a container for each session
The killer feature that could benefit the most amount of people would be server migrations during spot interruptions, although admittedly that would be complex to pull off (especially in real-time).
SpacetimeDB is not related to multiple servers. It's a server and a database combined.
I don't think you need to spawn multiple servers for it. Why open multiple sockets?
You can have a single server with clients split by rooms.
Well multiple sockets could be an incremental upgrade if you were moving from a single-tenancy model to a multi-tenancy one. Your matchmaker wouldn't need to know the difference and you're theoretically only changing the game server code to support it and not necessarily the client side.
I don't think this is a particularly strong argument in favor of multiple server entities, just a potential use case since the feature exists now.
But yeah, in a fresh environment, I think you could probably manage using rooms assuming you can segment rooms if you're using them per instance too.
Oh I guess another scenario is that you're using an off-the-shelf matchmaking service that is expecting different ports per host.
You usually can define the port. It could be the same port since you also include some special authentification data. This authentification data could be used to assign a room.
no worries, best way to learn is by having these kinds of conversations.
I actually have a question about replicon. How does the server replicate messages to the clients?
Is it done in a single-threaded way where all messages are written in a single buffer, and then the final message for each client is built from that using index ranges into the big buffer?
Can a message be serialized once and then the bytes be re-used across clients? That's what I had in a previous version of lightyear but I switched to a parallel approach where the messages for each client are written independently in parallel. Not sure what's better
We re-use serialized data.
When we iterate over things and the data considered relevant for a client, we store this range in an Option. Next client will just re-use the range.
And the client doesn't re-use the range if the data potentially contains entity-mappings?
I know, but persistent games are databases. And some of these games also need zone sharding. The point I'm trying to make is that having multiple servers could be useful for that, but I'm no expert on this topic. And if you're making that big of a game you would probably roll your own networking stack
Everything is re-used except removals and despawn caused by visibility changes (because it's client specific). But we serialize server entities inside components as is.
Clients map the entities during deserialization. If it's a new entity, it will be spawned and the mapping will be remembered. If it was previously spawned, the data goes into the existing entity.
For signatures, they are simply located at the beginning of the message, so the client deserializes hash<->entity and maps them before processing the rest of the data.
As for performance, I haven't tried multiple threads, so I can't say for sure.
But I found that writing to a single buffer sequentially is faster than using many small buffers for different things. And replication systems don't require full world access, so they could run in parallel with other logic.
That make sense; I had something similar but I had to move away from this to support bi-directional replication without code duplication.
There's so many choices and trade-offs 😅
But yeah i'm down to try to integrate with replicon, but it's not really on my list of priorities. I'd be open to PRs opening a lightyear_replicon crate that replaces lightyear_messages, lightyear_replication.
The absence of delta compression doesn't matter at all, it's pretty niche
Interesting, I wonder I'll be able to keep this approach 🤔
Glad you like it!
But same here - there are many other things in my priority list right now... I feel like I never make my game this way 😅
Maybe you could open an issue in case some brave soul decides to tackle it?
is lightyear syncs entity ? for example if i send a message containing entity, can i use it on server to find the same entity ?
yes, entities are mapped within messages
within components as well, right? using the EntityMapper(if it was called that). I haven't used lightyear for a few weeks and it' used to move/iterate fairly quickly xD
With this in mind: There is barely much differentiation between developing a networked and local (not networked) game with lightyear because entity mapping of messages and components is possible. That seems fairly nice
Is it possible to have a Player entity where some components are server-authoritative (Position, Transform) and others are client-authoritative (PlayerName)?
Use case: Client should be able to update their own username, which then gets rebroadcasted to all other clients. Right now I'm using a message-based approach(client sends UpdateUsernameMsg -> server iterates over player entities to find associated player -> server updates PlayerName -> replicates to all), but it requires inefficient entity relationship lookups.
Ideal flow would be:
- Client inserts/updates PlayerName on their predicted player entity
- Server accepts the client-authoritative component update
- Server replicates to all other clients
If this mixed authority isn't possible, what's the recommended pattern for client-initiated updates to replicated entities? The current message-based approach works but feels clunky when the client just wants to "set a field" on their own player.
Currently i think the message-based approach is correct.
The mixed authority idea is interesting, but it's not really a priority right now
hello i'm trying to write a server implementation without enabling rendering, and i'm using gltf files to spawn a scene
how can i do it without rendering the server ?
All examples do just that, they run the server without rendering. You can spawn an app with no rendering like this: https://github.com/cBournhonesque/lightyear/blob/main/examples/common/src/cli.rs#L334
@pine cape when i spawn a gltf scene with ColliderHierarchy, some of the meshes in the scene starts rotating
I think something is wrong with the lightyear physics
What replication mode are you using
app.add_plugins(lightyear::avian3d::plugin::LightyearAvianPlugin {
replication_mode: AvianReplicationMode::PositionButInterpolateTransform,
..default()
});
app.insert_resource(avian3d::physics_transform::PhysicsTransformConfig {
transform_to_position: true,
position_to_transform: true,
..default()
});
app.add_plugins(
PhysicsPlugins::default()
.build()
// disable the position<>transform sync plugins as it is handled by lightyear_avian
.disable::<PhysicsTransformPlugin>()
.disable::<PhysicsInterpolationPlugin>()
.disable::<IslandPlugin>()
.disable::<IslandSleepingPlugin>(),
);
also one more question, when player is idle client still sends input messages
I believe PositionButInterpolateTransform doesn't work well with entity hierarchies unfortunately..
Could you try using AvianReplicationMode::Transform instead?
I fixed that rotating entity problem by adding colliderconstructor and rigidbody to each mesh while spawning them
Yes there is an issue about pausing input messages it the ActionState is disabled (leafwing) or if the context is paused (BEI) but it hasn't been implemented
oh okay thank you
I've been getting these on the server. It still sees input but doesn't apply them.
2025-12-16T00:42:54.807149Z DEBUG lightyear_inputs::input_message: Mismatch detected at tick Tick(19) for new_input Some(ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }). Previous predicted input: None
... etc (every tick)
2025-12-16T00:43:01.907398Z DEBUG lightyear_inputs::input_message: Mismatch detected at tick Tick(161) for new_input Some(ActionsSnapshot { state: Fired, value: Axis2D(Vec2(1.0, 1.0)), time: ActionTime { elapsed_secs: 0.85000014, fired_secs: 0.85000014 }, events: ActionEvents(4) }). Previous predicted input: None
... etc (every tick)
2025-12-16T00:43:03.507339Z DEBUG lightyear_inputs::input_message: Mismatch detected at tick Tick(193) for new_input Some(ActionsSnapshot { state: None, value: Axis2D(Vec2(0.0, 0.0)), time: ActionTime { elapsed_secs: 0.0, fired_secs: 0.0 }, events: ActionEvents(0) }). Previous predicted input: None
How can I debug this myself? It seems related to my past issues (Axis2D vs bool/inputs not coming through/inputs on server applying old input even when let go or changed direction).
I'm using ly main branch.
These are not bugs and are completely expected.
Basically the client predicts that every other clients keeps pressing their last pressed input.
If another client changes their input, then you would get a mismatch compared to what you were predicting for them, which causes a rollback
I was playing around with the "avian_physics" example in lightyear to get a feel for the library and the unmodified example always eventually crashes after I play around with two clients for a while. Sometimes, the server crashes with the same message too, but this time it survived.
Client crash logs: https://pastebin.com/SBgtAtr0
thread 'main' (41699) panicked at /home/wurst/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/avian2d-0.4.1/src/dynamics/solver/islands/mod.rs:650:9:
assertion failed: island.contact_count > 0
When I tried out another crate for networking, bevy_rewind (using replicon), I saw that they disabled the island solver:
app.add_plugins(
PhysicsPlugins::new(SimulationPostUpdate)
.build()
.disable::<IslandPlugin>()
.disable::<IslandSleepingPlugin>(),
)
I did not experience these crashes in the toy_cars example from bevy rewind. Does anyone know more about this? Should I disable the island solver for my own game, or is this crash caused by something else?
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
It's an oversight in the lightyear example; the island plugins need to be disabled to work properly.
The reason is that avian maintains some internal state for the island plugins that are not updated properly on rollback and cause crashes
Replicate::handle_connection was matching on replicate.mode even when handling PredictionTarget / InterpolationTarget; using the passed-in mode fixes cases where entities were incorrectly marked as both predicted and interpolated, which caused invalid spawn actions and a serialization crash. I’ve opened a PR with the fix.
thanks!
So I think due to rewrites or something this kind of log spam is happening to me.
https://github.com/cBournhonesque/lightyear/issues/322#issuecomment-2108675797
After looking into it and looking through the discord it seems to be a windows issue as mentioned here #1189344685546811564 message
I can open an issue on github if needed. The fix could either be would @long marsh mentioned OR the UDP socket on windows has to be created with the SIO_UDP_CONNRESET flag set to false I think?
https://learn.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls#sio_udp_connreset-opcode-setting-i-t3.
I guess this can also be handled by removing the peer who is being set to?
Another issue is that because the error originates in the receive function in recv_from it gets to it, breaks, and just stops receiving messages right?
Have I mentioned that I hate Windows
I don't have a windows machine so it's hard for me to test, but yes please open an issue!
how do one get a client only camera thats frame interpolated using lightyear avian? now its kinda living its own life again. seems to be just 1 game loop behind? I do want it to be client only, so I guess i just gotta put it in the right schedule with with the right after/before conditions? everything else seems to work very smoothly. I did have it working well some time back but i just teleported from bevy 0.14 to 0.17 recently, so i think some of the stuff I did correctly previously has been lost in new changes. usually it was on following avian physics to have it synced but not i see it relies on lightyear_avian. Right now my shared player_movement and client camera movement is on the schedule FixedUpdate.
camera system runs like this: move_camera.after(shared::player_movement) in the FixedUpdateschedule. am i thinking correctly?
No camera should be in PostUpdate, after TransformPropagate. The camera should be updated on every frame. And if the camera is following an entity that is updated in FixedUpdate, the camera value should be set to follow that entity after FrameInterpolation is applied
Hello, I'm currently developing a voxel sandbox game with a server-client architecture and I'm having a hard time organizing my code.
Some components are server-side only, others are client-side only, some are shared, others are shared and replicated, and others are shared but not replicated. I think that, unlike your examples, I will not separate the rendering code, because only the client will perform the rendering, so there is no need to see the server's point of view (this may be a bad idea, please correct me if I'm wrong).
I have thought of these two ways to structure my project:
#1
.
└── features
├── feature1
│ ├── client.rs
│ ├── mod.rs
│ ├── protocol.rs
│ ├── server.rs
│ └── shared.rs
├── feature2
│ ├── client.rs
│ ├── mod.rs
│ ├── protocol.rs
│ ├── server.rs
│ └── shared.rs
├── feature3
│ ├── client.rs
│ ├── mod.rs
│ ├── protocol.rs
│ ├── server.rs
│ └── shared.rs
└── mod.rs
#2
.
├── client
│ ├── features
│ │ ├── feature1.rs
│ │ ├── feature2.rs
│ │ ├── feature3.rs
│ │ └── mod.rs
│ └── mod.rs
├── protocol
│ ├── features
│ │ ├── feature1.rs
│ │ ├── feature2.rs
│ │ ├── feature3.rs
│ │ └── mod.rs
│ └── mod.rs
├── server
│ ├── features
│ │ ├── feature1.rs
│ │ ├── feature2.rs
│ │ ├── feature3.rs
│ │ └── mod.rs
│ └── mod.rs
└── shared
├── features
│ ├── feature1.rs
│ ├── feature2.rs
│ ├── feature3.rs
│ └── mod.rs
└── mod.rs
In the first structure, I think it is easier to navigate and maintain each feature, while the second structure allows each top-level module to be converted into a crate.
Do you have any suggestions? There are few open source games that use lightyear, and even fewer that are well organized.
I think both could work and it's mostly a matter of preference! I mostly use option 2
@pine cape I noticed that when input locks up, I am getting new errors on 0.25:
2025-12-28T19:59:31.268054Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.333822Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.399574Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.464628Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.531183Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.596866Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.662778Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.730063Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
2025-12-28T19:59:31.794167Z WARN bevy_enhanced_input::action::fns: action `PlayerMovement` (`237v1`) expects `Axis2D`, but got `Bool`
Very strange since PlayerMovement is not a bool. Why would it be broadcasting as a bool?
use bevy::prelude::*;
use bevy_enhanced_input::prelude::*;
#[derive(Debug, InputAction)]
#[action_output(Vec2)]
pub struct PlayerMovement;
That's a quirk with BEI where by default the inputs are replicated as 'bool'; normally it should only happen a few times, until the first time the real input value gets replicated
The way that I've organized my project is pretty different from these.
.
└── src
├── bin
│ ├── client.rs
│ └── dedicated_server.rs
├── core
│ ├── physics.rs
│ ├── protocol.rs
│ └── mod.rs
├── data
│ ├── actions.rs
│ ├── components.rs
│ ├── events.rs
│ ├── states.rs
│ └── mod.rs
├── systems
│ ├── feature1.rs
│ ├── feature2.rs
│ ├── feature3.rs
│ └── mod.rs
└── lib.rs
Source files in data have this kind of structure:
// Anything at this level is server + client
pub struct SomeComponent;
#[cfg(feature = "client")]
pub use client_only::exports::*;
#[cfg(feature = "client")]
pub(super) mod client_only {
use super::*;
pub mod exports {
// Anything at this level is client only
use super::*;
pub struct SomeClientComponent;
}
}
Source files in systems have this kind of structure:
use crate::prelude::*;
pub(super) fn plugin(app: &mut App) {
app.add_plugins(some_system);
}
// systems and observers at this level are shared server + client
// and can be scheduled differently in the client module and using run conditions
fn some_system(...) { ... }
#[cfg(feature = "client")]
pub(super) mod client_only {
use super::*;
pub fn plugin(app: &mut App) {
app.add_plugins(some_client_system);
}
fn some_client_system(...) { ... }
}
let me know if you need more elaboration with the way the mod files are structured to set up the module visibility and plugin loading, but this hopefully gives you enough to determine if it's a good organizational pattern for you.
Hi, I'm brand new to lightyear, still wrapping my head around things.
What I want is for my server to tell the client what map to load, then the client to wait for that map to load, and then for replication to start. So should I have the server send a "greeting" message to the client, and then the client to send a "loaded" message back to the server once it's ready, and then enable replication for the client or something?
Is there a better way to do this?
how can i replicate positions of a mass amount of entities with small bandwidth ?
Yes that sounds correct