#lightyear

1 messages ยท Page 12 of 1

pure drum
#

The reason is I have other entities that don't live in the avian physics world

#

Thanks, I'll check it out

#

Ooops.. I just made the repo public

#

legend! that seems to work!

#

So looks like PhysicsSet::Sync -> PredictionSet::UpdateHistory is the key here IIUC

pine cape
#

Yes, i'm actually not even sure why the PredictionSet::UpdateHistory -> PhysicsSet::Sync order works when replicating Position/Rotation.
Intuitively it would seem like you would need to first do a physics update (update Position/Rotation) and then update the history.
I must have had a reason but I cannot recall it right now

pine cape
#

I just finished re-adding Host Client mode!
I only enabled it for the simple_box example for now; you can test it with cargo run -- host-client -c ID (and cargo run -- client -c ID to spawn another client).
Does someone experienced with bevy UI know why the 'Disconnect' button does not work in host-client mode?

The Interaction component seems to pick up the button click, but the observer doesn't get triggered for some reason

earnest fog
pine cape
#

it's possible; there's a lot of subtle edge-cases and i haven't ported all the tests over from the previous version.
I'm porting all the other examples, which will help unearth more issues: https://github.com/cBournhonesque/lightyear/pull/1029

GitHub

Remaining issues:

avian physics: are host-client inputs broadcasted properly? heavy rollbacks
client-replication:

host-client isn't running interpolation
host-client cursor isn't ...

#

I think the issue is race conditions around observer ordering

#

i'm happy we are approaching feature-parity with before the refactor

earnest fog
#

yes, it's looking really good. Good work

earnest fog
earnest fog
pine cape
#

oh

earnest fog
#

The server ui node spawns on top and captures all of the input events

#

I saw an issue about this in the past where you have overlapping nodes that don't pass the input through. I'm looking for it now

pine cape
#

I guess we also don't need a Node that takes up the whole screen

#

we could just position directly the buttons/text

earnest fog
#

That would be the best since they're currently not parented to the same root node.

#

Yep. Changing the width for both buttons to %50 works

#

Also had to add justify_self: JustifySelf::End, to the client node

#

I'll open a PR

pine cape
#

perfect, thanks

pine cape
#

are you sure this works? i still can't seem to click on disconnect

#

in host-server mode

earnest fog
#

If I add logging, I definitely get a button click that wasn't working before. The actual disconnect I think is still broken

lyric badge
#

trying to connect between to machines, but i get this error:

ERROR system{name="lightyear::client::networking::receive_packets"}: lightyear::connection::netcode::client::connection: error updating netcode client: 
Transport(Io(Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." }))
#

is this a firewall issue?

woven mortar
lyric badge
#

sorry, im on windows

woven mortar
#

The error message is saying the connection was closed by the remote host (probably the server). Did you configured the TLS for the server? I think it is necessary to make the handshake in QUIC.

#

I didn't tried the library yet, but it sounds it is missing a configuration in the server side, then it is closing the connection.

lyric badge
#

i see, im not sure i did that, ill check it out though

woven mortar
pine cape
#

This doesn't look like a certificate issue though

lyric badge
pine cape
#

it might be a firewall issue; are you using udp? where are the machines located

lyric badge
#

udp, yes

#

buddy is helping me test it, we are in different cities

pine cape
#

it's his personal machine? yeah i think you'll run into firewall issues

lyric badge
#

he turned off his firewall but we still couldnt connect

pine cape
#

you might need to do NAT traversal; it's not straightforward

#

you want your game to be client-server?

#

the easiest to test is to get a cheap box with a provider (hertzner, digital ocean, etc.)

#

all of them offer credits which give you 1 month free

lyric badge
lyric badge
pine cape
#

maybe; you also might not be doing it correctly

lyric badge
#

why maybe?
also we've setup minecraft/terraria servers before with port forwarding, we did it the same as that basically

pine cape
lyric badge
#

fair enough

#

thank you

earnest fog
# pine cape are you sure this works? i still can't seem to click on disconnect

I figured it out. It's because the disconnect fn query for client_plugin expects a NetcodeClient from the Client entity, but when the simple_box sets up the Server and Client, it spawns a Client without the NetcodeClient. NetcodeClient is setup by the ExampleClient in common but the simple_box host-client mode doesn't use the ExampleClient

pine cape
#

oh great catch

#

so the disconnect does nothing because there is no NetcodeClient

#

I need a specific Disconnect observer for HostClients

earnest fog
#

Exactly, it's calling the observer but doesn't find the component on the triggered entity

pine cape
#

I do already have a special connect observers just for host-client, so this makes sense

pine cape
#

thanks! LGTM

pine cape
slow kernel
#

has anyone looked into using bevy_enhanced_input with lightyear? it's probably easier for me to rewrite my game to use leafwing than try to update lightyear, but I was just curious if anyone has looked at it

autumn furnace
#

enhanced input seems is more likely the future standard

#

so you may not want to switch to lightyear

#

@pine cape might be able to advise if lightyear is doing anything particularly special with it's builtin lightyear support.

#

ie. whether something similar can't be done third party for enhanced input

slow kernel
#

that's why I had been writing my newest game using BEI, but lightyear seems like the best fit for my needs: replication, client side prediction+interpolation+correction, and lag compensation

#

I've done some of that myself in the past in fyrox and it's complicated enough without all the additional system ordering issues and abstractions

#

unless there's an active replicon crate with similar functionality I'll probably stick with lightyear for now and switch to leafwing if necessary

pine cape
#

It's something I want to support

slow kernel
#

do you anticipate it would require changing anything other than light_year_inputs_leafwing? not sure how deeply coupled it is with those features or anything

#

I know there are unique ordering issues etc

#

I probably need to research more about how they differ tbh

pine cape
#

I've tried to make lightyear_inputs abstracted away from the underlying impl; it requires an ActionStateSequence (https://github.com/cBournhonesque/lightyear/blob/main/lightyear_inputs/src/input_message.rs#L39)
and I currently have 2 implementations, one for leafwing and one for native inputs https://github.com/cBournhonesque/lightyear/blob/main/lightyear_inputs_native/src/input_message.rs#L16

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

#

(the Sequence part is so because I want to send the inputs for multiple ticks for redundancy, and each implementation needs to specify how the Inputs for one tick can be computed from the previous tick, in case you can optimize the serialization using diffs)

slow kernel
#

makes sense

pine cape
slow kernel
#

If I'm able to make any progress with BEI are there tests that would help me verify things?

#

also what state is main in currently? I know you just did a major refactor, but do the client server features seem to be working and are tests up to date?

pine cape
#

Main is in a pretty good state, almost all examples work and I ported a lot of the tests. You can just go in lightyear_test and run cargo test

I've added some tests for input handling but those are a bit tricky

hot cloud
#

Hello!
I am using bevy 0.16.1 and lightyear 0.20.2. I run the client and the server in the same app but in a separate thread. I tried to add a lifetime to bullets, with the shooting mechanic being inspired by the "fps" example in the lightyear repo. I have this shared method system:

pub(crate) fn lifetime_despawner(
    q: Query<(Entity, &Lifetime)>,
    mut commands: Commands,
    tick_manager: Res<TickManager>,
    identity: NetworkIdentity,
) {
    for (e, ttl) in q.iter() {
        if (tick_manager.tick() - ttl.origin_tick) > ttl.lifetime {
            if identity.is_server() {
                // info!("Lifetime despawning server-side {e:?}");
                commands.entity(e).despawn();
            } else {
                // info!("Lifetime predicted despawning client-side {e:?}");
                commands.entity(e).prediction_despawn();
            }
        }
    }
}

On the shoot_bullet shared function, I simply add my Lifetime struct:

// Inside the `shoot_bullet` shared system
// [...]
let bullet_bundle = (
  Name::new("Bullet"),
  Bullet,
  // store the player who fired the bullet
  BulletOwner(id.0),
  bullet_transform,
  LinearVelocity(bullet_direction * BULLET_MOVE_SPEED),
  RigidBody::Kinematic,
  // Lifetime component here
  Lifetime {
    origin_tick: current_tick, // tick_manager.tick()
    #[allow(clippy::cast_possible_truncation)]
    lifetime: (FIXED_TIMESTEP_HZ as i16) * 2,
  },
);

Every other launch of the game, the app will crash when there is an attempt to predict despawn a bullet and I get this error:

#
   0: bevy_ecs::system::function_system::system_commands
           with name="shared::player::lifetime_despawner"
             at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/system/function_system.rs:65

thread 'main' panicked at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/error/handler.rs:141:1:
Encountered an error in command `<<bevy_ecs::system::commands::EntityCommands as lightyear::client::prediction::despawn::PredictionDespawnCommandsExt>::prediction_despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 62v2 was despawned by /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lightyear-0.20.2/src/client/prediction/despawn.rs:89:24

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `shared::player::lifetime_despawner`!
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`!

The problem is, it doesn't happen every time. On the other hand, it's either going to crash right from first bullet thrown when it should despawn, or not at all and works for as many bullets I fire. I've tried lots of things without success. Does anyone have any clues as to what might be the problem? The fact that this doesn't happen everytime I start the game disturbs me.

pine cape
#

I'll release a patch

hot cloud
#

Thanks a lot for the quick reply and help!

pine cape
vocal sentinel
#

๐Ÿ‘‹ is Lightyear in some kind of weird state right now? I'm getting panics or failures to compile on almost every example as of v0.21.0-rc.1.

pine cape
vocal sentinel
#

Ahhhh got it, I'll try main out

#

oh this was literally a month ago that explains it

hot cloud
#

I just tried the 0.20.3 version to see if it fixed my issue with lifetime prediction despawn of a bullet, but I still have the issue sadly. (I ran cargo clean before trying again)

0: bevy_ecs::system::function_system::system_commands
           with name="shared::player::lifetime_despawner"
             at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/system/function_system.rs:65

thread 'main' panicked at /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/error/handler.rs:141:1:
Encountered an error in command `<<bevy_ecs::system::commands::EntityCommands as lightyear::client::prediction::despawn::PredictionDespawnCommandsExt>::prediction_despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 62v2 was despawned by /Users/klaus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/system/commands/mod.rs:1802:28

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `shared::player::lifetime_despawner`!
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`!

It's probably a problem on my side but I have no idea where it's coming from

pine cape
#

Maybe you need to do commands.get_entity(e) instead of commands.entity(e) ?
Otherwise I would suggest upgrading to the latest version

hot cloud
#

ok thanks. I will try to update, I would have had to do it at some point anyway

buoyant trellis
#

Is there a way to tell on the server when I am good to send mapped entities to the client? i.e. once the client knows about it and can map it successfully

feral mirage
#

does the confirmed entities really need to be separate entities instead of having a component like Confirmed::<Transform>?

#

right now i have many system that check for a Added<C> but that then needs to check if it is the predicted or interpolated and ignore if it is confirmed

pine cape
pine cape
feral mirage
#

on the server there is no predicted, confirmed, or interpolated, so it's systems wouldn't have any filters, on the client you will most likely only work on the predicted and interpolated entities, always filtering out the confirmed, and letting lightyear itself handle the confirmed entity

#

right?

#

other topic, is there a message to indicate a client left a room?

pine cape
pine cape
feral mirage
feral mirage
#

My view of having it as a Confirmed::<C> would be exactly so that there would be no need for filters, but investigation is needed

unkempt sedge
#

@pine cape Ran into an interesting bug I have this filter
mut carrier: Query<(Entity, &mut WeaponCarrier), (Added<WeaponCarrier>, Without<Confirmed>)>,
Interesting enough, if I run this system on first replication it works fine. But if I run in room transition it also considers the confirmed entity.
But
mut carrier: Query<(Entity, &mut WeaponCarrier), Or<(Added<Predicted>, Added<Interpolated>)>>,
This one doesnt working as expected

pine cape
#

Do you have a minimum reproducing example

unkempt sedge
pure drum
#

Is this the right way to set up a custom relationship (0.20.2)? It works only sometimes, maybe some race condition. (I've spent way too long getting it this far!)

When it doesn't work, the entity loses it's ChildOf component and get these errors:

2025-06-21T00:38:13.666154Z ERROR lightyear::shared::replication::entity_map: Failed to map entity 394v1#4294967690
2025-06-21T00:38:13.669482Z  WARN bevy_ecs::relationship: lightyear-0.20.2/src/shared/replication/hierarchy.rs:173:45: The bevy_ecs::hierarchy::ChildOf(PLACEHOLDER) relationship on entity 493v14#60129542637 relates to an entity that does not exist. The invalid bevy_ecs::hierarchy::ChildOf relationship has been removed.
    app.add_plugins(RelationshipSendPlugin::<CurrentlyControlledPawn>::default());
    app.add_plugins(RelationshipReceivePlugin::<
        ServerConnectionManager,
        CurrentlyControlledPawn,
    >::default());
    app.add_plugins(RelationshipReceivePlugin::<
        ClientConnectionManager,
        CurrentlyControlledPawn,
    >::default());
    app.register_type::<RelationshipSync<CurrentlyControlledPawn>>();
    app.register_component::<RelationshipSync<CurrentlyControlledPawn>>(
        ChannelDirection::ServerToClient,
    )
    .add_prediction(ComponentSyncMode::Full)
    .add_map_entities();

when spawning on the server:

commands
    .spawn((
        Name::new("Crew"),
        replicate.clone(),
        RelationshipSync::<CurrentlyControlledPawn>::from(Some(network_player_entity)),
        CurrentlyControlledPawn(network_player_entity),
        RelationshipSync::<ChildOf>::from(Some(*test_ship)),
    ));

#

also happy to wait for 0.21 if there are related fixes/changes

pine cape
#

Are both your entities in the same ReplicationGroup?

#

To guarantee that they will be replicated in the same message?

#

I might add a system to guarantee this

#

Otherwise one entity could be replicated before the order and the relationship insertion would fail

pure drum
#

ah, no they're not!

#

i'll try it out

pine cape
#

That's almost certainly the reason

pure drum
#

many thanks, that seems to work! such a simple solution

unkempt sedge
#

Why ClientConnectionManager does not have the capability of sending message to room?

pine cape
#

Because the client is not aware of rooms, only the server is

unkempt sedge
pine cape
#

No it also maps from confirmed to predicted and interpolated

unkempt sedge
pine cape
#

Hm yeah you would have to manually map it I guess

unkempt sedge
buoyant trellis
hot cloud
#

hello! I am trying to migrate to lightyear 0.21.0-rc1 (so many things changed! :o), how can we replicate resources on this new version? Couldn't find anything in the docs

pine cape
pine cape
# hot cloud hello! I am trying to migrate to lightyear 0.21.0-rc1 *(so many things changed! ...

Hi, unfortunately it's not possible to replicate resources in the new version.
The reason is that previously resources were replicated as messages, which came with its set of limitations.
In the next bevy version there are plans to make resources components, so they would be networked as components. For that reason i'm currently holding off on porting the implementation of resource-replication since it will become obsolete as soon as bevy 0.17 is released

pine cape
# unkempt sedge Hmm interesting how did you manage to do the InputMessage logic them? How does i...

The way things work is that the receiver maintains an EntityMap from remote_id to local_id. (i.e. from server entity to the client Confirmed entity): https://github.com/cBournhonesque/lightyear/blob/775248fccd42c900fef5f4576a53a243f7d3e1d6/lightyear_messages/src/lib.rs#L79-L79
Whenever you send any message, I apply the map_entities fn associated with the message to perform the mapping.

If you want to send a message containining references to Predicted entities, I would first apply map_entities to the message using the predicted entity_map (https://github.com/cBournhonesque/lightyear/blob/main/lightyear_prediction/src/manager.rs#L55)

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

pine cape
slow kernel
hot cloud
unkempt sedge
unkempt sedge
#

@pine cape Is it possible to observe lightyear sent custom message/events ? Between sever client

pine cape
#

Yes, you would need to register with add_trigger instead of add_message

stray sinew
#

Does anyone know whats causing this error? This is running in the FixedUpdate schedule.

pub fn explode_shell_lifetime(
    mut commands: Commands,
    query: Query<
        (
            Entity,
            &DespawnAfter,
            &Position,
            &Velocity,
            &ChildOf,
            &Explosive,
            &ShellMarker,
        ),
        Or<(With<Predicted>, With<PreSpawned>, With<Replicate>)>,
    >,
    timeline: Single<(&LocalTimeline, Has<Server>), Without<ClientOf>>,
) {
    let (timeline, is_server) = timeline.into_inner();

    for (entity, despawn, position, velocity, child_of, explosive, shell_marker) in query.iter() {
        if (timeline.tick() - despawn.origin_tick) > despawn.lifetime {
            let salt = shell_marker.id + timeline.tick().0 as u64;
            info!("explosion salt: {}", shell_marker.id);

            let explosion_entity = commands
                .spawn(ExplosionBundle {
                    explosion_marker: ExplosionMarker,
                    explosive: explosive.clone(),
                    position: position.clone(),
                    velocity: velocity.clone(),
                    child_of: child_of.clone(),
                    child_of_sync: ChildOfSync::from(Some(child_of.0)),
                    prespawned: PreSpawned::default_with_salt(salt),
                })
                .id();

            if is_server {
                commands.entity(explosion_entity).insert((
                    Replicate::to_clients(NetworkTarget::All),
                    PredictionTarget::to_clients(NetworkTarget::All),
                    PreSpawned::default_with_salt(salt),
                ));

                commands.entity(entity).despawn();
            } else {
                commands.entity(entity).prediction_despawn();
            }
        }
    }
}
unkempt sedge
pine cape
#

Hm looks like PreSpawned is inserted several times in the network message from the server to the client, looks like a bug from my side

#

This is on which version?

buoyant trellis
#

I have a component (ShipGrid) that represents the voxels on some moving entity, each entity has a hashmap of Chunks, each Chunk is some fixed size (16x16x16) vec of voxels. Players can edit individual voxels. I want to add delta compression, since each instance of ShipGrid could contains tens of thousands of voxels.

Everything is controlled by the server so tracking changes on the server is trivial, I just write whatever changes I make to the voxels into a Vec called delta. Right now I am manually sending this delta Vec to clients whenever any edits are made however id prefer to use the built-in features that lightyear provides where possible. Is this a reasonable use-case for the type of delta compression that lightyear can do?

Not super familiar with the library so kind of stuck on the implementation of Diffable for a ShipGrid, for fn diff I would rather return the changes accumulated in the delta Vec instead of comparing two ShipGrids to each other, is this a problem? Additionally, Diffable requires the struct to also implement PartialEq, is this only used to see if a delta needs to be sent? In that case I could implement it in such a way to always return true when the delta Vec is non-empty, or would this cause problems?

Any other tips are also appreciated.

#[derive(Component, Debug, Clone, Reflect, Serialize, Deserialize)]
pub struct ShipGrid {
    pub chunks: HashMap<ChunkIndex, Chunk>,
    pub delta: Vec<ShipGridDelta>,
    pub dirty: bool,
}

#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
pub struct Chunk {
    pub blocks: Vec<Option<BlockData>>,
    pub dirty: bool,
}

#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
pub enum ShipGridDelta {
    AddBlock {
        pos: BlockIndex,
        block_data: BlockData,
    },
    RemoveBlock {
        pos: BlockIndex,
    }
}
pine cape
# buoyant trellis I have a component (`ShipGrid`) that represents the voxels on some moving entity...

Hi, yes I believe that would be a good use of DeltaCompression. To give you an example, I used it for my game cycles-io for bevy jam 5.
In that game players would leave a trail behind them (Vec<Point>), and I didn't want to replicate the entire trail at every new point, so instead I used delta-compression to only send the few points that were added since the last synced trail: https://github.com/cBournhonesque/jam5/blob/main/shared/src/player/trail.rs#L66

I hadn't expected that you could provide the diff without looking at the previous value, but I agree that it would be convenient for performance. I'm not convinced that it is correct though. The way delta-compression currently works is:

  • on the sender (server), i keep track of which ticks were fully ack-ed by the client. Then I complete a diff from the last acked tick (for example tick 5) and i send DiffFromTick5ForCurrentTick8
  • on the receiver (client), i keep a history of the component values since the last one that was acked by the server. For example I am on tick 12 and i have tick 5 in the history. When I receive DiffFromTick5ForCurrentTick8, i apply the diff to the stored component from tick 5 to get the value for tick 8.

I think that if you just use your delta Vec, there might be cases where things will get desynced; For example the DiffFromTick5ForCurrentTick8 message gets lost, so you now need to send DiffFromTick5ForCurrentTick12. Maybe there is a better way of doing this? I'm not sure.

And the PartialEq bound is for change-detection on the receiver; it's not super important, I can remove it.
Also please note that I haven't implemented/tested delta-compression in the main branch (after the refactor)

GitHub

Contribute to cBournhonesque/jam5 development by creating an account on GitHub.

#

In your case it might be simpler to send the delta at regular intervals using a reliable channel, and not network the component at all

buoyant trellis
# pine cape Hi, yes I believe that would be a good use of DeltaCompression. To give you an e...

I see, if we are comparing the current component to different past versions of it then my idea of implementing diffable would not work. I suppose one could keep track of the changes made each tick, and delete all older ones after all clients have acked some tick. Essentially some sort of sliding window, then if we know the ticks of the two versions we are comparing we could send the right deltas from the history. Could be useful in cases where we absolutely do not want to compute a diff if its too costly performance-wise.

But I will just stick to events on a reliable channel for this specific component for now, thanks for the detailed response :)

pine cape
#

I think a solution would be if the Diffable trait provided the user an opportunity to include the deltas + the tick information for the last few ticks.
Then upon receiving your delta I could ignore the ticks that are more recent than the ones that have been hacked.

buoyant trellis
#

So basically always broadcast the last few deltas (or whatever the user provides in the impl) and let the client-side filter out what has already been applied?

pine cape
#

Yes

buoyant trellis
#

Assuming we send the same deltas to all clients (ie not keeping track of what each client has already received), it should already be possible to implement that just through the Diffable trait (as long as we keep track of the tick at which we record our deltas at and do the filtering in the applyDiff impl) no? Either way it would not fit my usecase. I do hope I dont come across as demanding any work from you, I do appreciate the library you've written.

unkempt sedge
#

@pine cape Lets say I have an entity with two distinct leafwing action states inserted unto it, would that cause conflict on input buffering? Specifically if both input based system are in fixed_update. Note - I know this sounds weird but this is the only explanation i have for this bug

#

@pine cape If you would like I have an easy sample for you to test out this scenario

pine cape
pine cape
buoyant trellis
feral mirage
#

@pine cape are you keeping track of the changes i'm making for the PR to remove bevy proper from the crates?

feral mirage
pine cape
#

Damn, is it worth it? Lol

feral mirage
#

haven't tested

#

missing the root lightyear and lightyear_tests

#

so that i can then start on the examples and demos

#

will attach timins to the PR when it is done

#

from before and after the PR

pine cape
#

I don't think the examples and demos should the change

#

They should resemble code that users would use, and users would use the main bevy_crate

feral mirage
#

ah, yes, my mistake

#

no, actually, you will still have the subcrates compiling in parallel before bevy_pbr, so when it gets to lightyear all subcrates should be done

pine cape
#

Lightyear_tests doesn't need to change either

feral mirage
#

ah, you don't mean the compile time, you mean the Cargo.toml

#

there are 2 crates that the tests are super broken, right?

pine cape
#

Yea, probably, I didn't fix all tests in individual crates

feral mirage
#

lightyear_prediction and lightyear_frame_interpolation try importing types that appear nowhere

#

@pine cape are you available right now? can you run cargo test -p lightyear_netcode --all-features and see if any tests fail on main?

#

so that i can put #[ignore] for now

pine cape
#

the tests work but some doc tests fail

feral mirage
#
   Doc-tests lightyear_netcode

running 12 tests
test lightyear_netcode/src/client.rs - client::Client<Ctx>::with_config (line 266) ... FAILED
test lightyear_netcode/src/lib.rs - (line 47) ... FAILED
test lightyear_netcode/src/lib.rs - (line 83) ... FAILED

yeah, it is doctests on lightyear_netcode

feral mirage
#

@pine cape commented on the PR with 2 timings, one on main and one on my branch

feral mirage
#

@pine cape it is ready

unique plover
#

Is there a convenient way to go from PeerId to the Client entity in the refactor? I'm trying to add a client to a room later after they have acknowledged with a trigger, but the RemoteTrigger gives a PeerId and RoomEvent::AddSender is expecting a client entity.

stray sinew
# unique plover Is there a convenient way to go from PeerId to the Client entity in the refactor...

I use a component with the PeerID as a field which is added directly to the player entity the client controls

#[derive(Component, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CharacterMarker(pub PeerId);

I also use a PeerID - Entity map locally which is updated on connection and disconnection

#[derive(Resource, Clone, Serialize, Deserialize)]
pub struct CharacterMap {
    pub characters: HashMap<PeerId, Entity>,
}
unique plover
pine cape
pine cape
inner hill
#

Does avian still have rollback issues?

pine cape
#

Yes, because the server uses some Collisions resource that is not replicated or rolled back.
I think the rollbacks can be avoided by adding some tolerance on the rollback checks (for example only rolling back if the delta is above 0.1)

autumn furnace
#

I noticed in recent main branch, the avian_physics example disables avian's sync plugin

#

if this is handled by lightyear_avian, is that only for replicated entities or will lightyear_avian also reproduce the normal sync behavior for non-replicated entities

pine cape
#

It will also do the sync for all entities

#

The reason is to apply the sync for entities that don't have RigidBody, such as interpolated entities

autumn furnace
#

It looks in the code like it disables transform->position sync

#

is that the case?

#

OH, that's already in lightyear_avian in the version I'm working on

#

that guess that explains why replicating transform isn't working.

#

is there a problem with syncing transform?

unkempt sedge
#

Gotta say this is the first time i see the ci actually working, this must have been quite the improvement

#

also oh my hawd

pine cape
stray sinew
#

Is it normal behavior for confirmed entities to have the PreSpawned component?

pub fn print_entities_with_prespawned(
    q: Query<Entity, (With<PreSpawned>, With<Confirmed>)>
) {
    for entity in q.iter() {
        info!("Entity: {:?} has both PreSpawned and Confirmed", entity)
    }

}
pine cape
#

Yes, those were the PreSpawned entities on the client that got matched with a server entity

drifting prawn
#

I'm running into an odd issue, I'm pointed to the head of main (I know unstable, but wanted to jump the gun on 0.21 refactor; and what I am trying to do seems to be working in the avian_3d_character example).

I have mostly mimic'd the aforementioned example for all the netcode, but when I spawn my character on the server and it gets replicated to the client the Predicted entity is not getting the CharacterMarker (named Tag in my code) component cloned, so I cannot do any client side setup of the entity. The Tag does exist on the Confirmed entity

fn server_handle_connected(
    trigger: Trigger<OnAdd, Connected>,
    query: Query<&RemoteId, With<ClientOf>>,
    mut commands: Commands,
) {
    let Ok(client_id) = query.get(trigger.target()) else {
        return;
    };
    info!("Client connected with id: {client_id:?}");

    let entity = commands
        .spawn((
            Name::new("Character"),
            Tag::new(*client_id),
            Facing::default(),
            Actions::default_input_map(),
            Transform::from_xyz(5.0, 5.0, 0.0),
            Hittable::new(100.0),
            // children![HurtBox, Collider::capsule(0.25, 1.6)],
            //Networking
            Replicate::to_clients(NetworkTarget::All),
            PredictionTarget::to_clients(NetworkTarget::All),
            // PredictionTarget::to_clients(NetworkTarget::Single(client_id.0)),
            // InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(client_id.0)),
            ControlledBy {
                owner: trigger.target(),
                lifetime: Default::default(),
            },
        ))
        .id();

    error!("Created entity {entity:?} for client {client_id:?}");
    // load_character_asset(entity, &mut commands, false);
}

Any ideas what could cause this to happen? I made sure to enable replication,interpolation,prediction feature flags.

My GameClient also has

ReplicationReceiver::default(),
PredictionManager::default(),
InterpolationManager::default()
pine cape
#

Did you register a PredictionMode on Tag?
Are you using Tag in an observer? Can I see the code for that observer?

drifting prawn
# pine cape Did you register a PredictionMode on `Tag`? Are you using `Tag` in an observer? ...

Yes, Tag has Once for prediction and Interpolation.

        app.register_component::<character::Tag>()
            .add_prediction(PredictionMode::Once)
            .add_interpolation(InterpolationMode::Once);

I don't believe we are using Tag in any observers, but we have a system in update to try and add the client side components

fn client_handle_new_character(
    mut commands: Commands,
    character_query: Query<(Entity, Has<Controlled>), (Added<Predicted>, With<Tag>)>,
) {
    for (entity, is_controlled) in &character_query {
        load_character_asset(entity, &mut commands, is_controlled);
        error!("NEW TOON");

        if is_controlled {
            error!("CAM MAN ADDED");
            commands.entity(entity).insert((
                Actions::default_input_map(),
                children![GameCamera, Transform::from_xyz(0.0, 0.35, 0.0)],
            ));
        }
    }
}

This is the system I can never get to run since Tag never appears on the Predicted object

pine cape
drifting prawn
#

Looks like it is indeed printing that it sync'd; But the inspector clearly isn't

pine cape
#

do you think you could privately share your repo with me? otherwise i can guide you on what logs to add

drifting prawn
pine cape
#

Can you try replcaing that part of the code with

    let components: Vec<ComponentId> = entity_ref
        .archetype()
        .components()
        .filter(|id| {
            let ok = prediction_registry
                .get_prediction_mode(*id, &component_registry)
                .is_ok_and(|mode| mode != PredictionMode::None);
            let kind = component_registry.component_id_to_kind.get(id).unwrap();
            let name = component_registry.serialize_fns_map.get(kind).unwrap().type_name;
            info!(
                "Checking if we should sync component {name:?} from confirmed {confirmed:?} to predicted {predicted:?}: should_sync = {ok}",
            );
            ok
        })
        .collect();
drifting prawn
#

Let me find out which

pine cape
#

hm i guess it's expected; because some components are not registered

#

maybe just do a if let Some() for the first check

drifting prawn
#

Ok, I'll fix

#

Tag is set to false !!

#

Also I have another type that is mine also set to false.

#

and both are registered

pine cape
#

so the PredictionMode is somehow None?
In which order do you register your plugins

drifting prawn
#

Protocol is before Server or Client.

pine cape
#

or are you sure your ComponentRegistry is not reset somewhere

drifting prawn
#

Only place it could maybe be changed would be with Skein, other than that my code itself doesn't touch that. I also wouldn't expect Skein to clear that, since it's the blender plugin.

#
fn runtime(runtime: Runtime) {
    let mut app = App::new();

    app.add_plugins((
        DefaultPlugins.set(AssetPlugin {
            watch_for_changes_override: Some(false),
            ..Default::default()
        }),
        EguiPlugin {
            enable_multipass_for_primary_context: true,
        },
        GameCameraPlugin,
        HanabiPlugin,
        FpsOverlayPlugin {
            config: FpsOverlayConfig {
                text_config: TextFont {
                    font_size: 24.0,
                    ..Default::default()
                },
                text_color: Color::srgb(0.0, 1.0, 0.0),
                enabled: true,
                refresh_interval: Duration::from_secs_f32(0.1),
            },
        },
        PhysicsPlugins::default()
            .build()
            .disable::<SyncPlugin>()
            .disable::<PhysicsInterpolationPlugin>()
            .disable::<SleepingPlugin>(),
        PhysicsDebugPlugin::default(),
        SkinnedAabbPlugin,
        SkeinPlugin::default(),
        graybeard::abilities::AbilityPlugin,
        graybeard::util::ttl::TimeToLivePlugin,
        graybeard::protocol::ProtocolPlugin,
        graybeard::level::LevelPlugin,
    ));

    app.insert_resource(SleepingThreshold {
        linear: -0.01,
        angular: -0.01,
    });

    app.add_systems(
        PostUpdate,
        position_to_transform
            .in_set(PhysicsSet::Sync)
            .run_if(|config: Res<avian3d::sync::SyncConfig>| config.position_to_transform),
    );

    app.add_systems(PostStartup, set_window_title);

    match runtime {
        Runtime::Server { addr } => {
            app.add_plugins(graybeard::server::ServerPlugin { addr });
        }
        Runtime::Client { server_addr } => {
            app.add_plugins(graybeard::client::ClientPlugin { server_addr });
        }
        Runtime::ClientHost { addr } => {
            app.add_plugins((
                graybeard::server::ServerPlugin { addr },
                graybeard::client::ClientPlugin {
                    server_addr: "127.0.0.1:63436".parse().expect("DED PARSE"),
                },
            ));
        }
    };

    app.run();
}

pine cape
#

i would try to see if the ComponentRegistry is empty after your ProtocolPlugin, and then after your Server/Client plugins

drifting prawn
#

That seems odd because Name is getting sync'd and it's set in protocol. At least to the Confirmed ahh but so is tag, it's just Predicted thats goofed

#

Let me add you to the repo maybe it's easier to poke around

pine cape
#

ah found it

#

looks like the PredictionRegistry needs to be present before ProtocolPlugin is called

drifting prawn
#

Ahh

#

So should I add protocol after server/client?

pine cape
#

it's annoying that bevy doesn't have dependencies between plugins

drifting prawn
#

Yea, totally.

pine cape
#

yes, protocol should be added last

drifting prawn
#

Ok, will give that a go.

pine cape
#

or move everything in your protocol from Plugin::build to Plugin::finish

drifting prawn
#

Maybe a panic for now if that's empty? I like fail fast

pine cape
#

yep, will do

drifting prawn
#

๐Ÿ‘

#

Hmm I'm getting a panic when swapping order.

pine cape
#

what panic

drifting prawn
#
2025-06-29T17:51:59.730889Z TRACE lightyear_prediction::registry: Adding prediction for component "lightyear_replication::hierarchy::RelationshipSync<bevy_ecs::hierarchy::ChildOf>" with mode Once

thread 'main' panicked at /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/world/mod.rs:356:57:
called `Result::unwrap()` on an `Err` value: ArchetypeExists(ComponentId(415))
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/4d08223c054cf5a56d9761ca925fd46ffebe7115/library/std/src/panicking.rs:697:5
   1: core::panicking::panic_fmt
             at /rustc/4d08223c054cf5a56d9761ca925fd46ffebe7115/library/core/src/panicking.rs:75:14
   2: core::result::unwrap_failed
             at /rustc/4d08223c054cf5a56d9761ca925fd46ffebe7115/library/core/src/result.rs:1762:5
   3: core::result::Result<T,E>::unwrap
             at /nix/store/9kizz11q3l2qllq8lnk88zb48bz4blg8-rust-mixed/lib/rustlib/src/rust/library/core/src/result.rs:1167:23
   4: bevy_ecs::world::World::register_required_components
             at /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/world/mod.rs:356:57
   5: bevy_app::app::App::register_required_components
             at /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_app-0.16.1/src/app.rs:838:26
   6: lightyear_messages::client::<impl lightyear_messages::registry::MessageRegistration<M>>::add_client_direction
             at /home/zach/.cargo/git/checkouts/lightyear-1dda86c04cfd7c75/84efa4b/lightyear_messages/src/client.rs:15:22
   7: lightyear_messages::client::<impl lightyear_messages::registry::MessageRegistration<M>>::add_client_direction
             at /home/zach/.cargo/git/checkouts/lightyear-1dda86c04cfd7c75/84efa4b/lightyear_messages/src/client.rs:22:22
   8: lightyear_messages::registry::MessageRegistration<M>::add_direction

#

Weird too the aviand3d example has protocol before client/server

pine cape
#

nope, the plugins are added in cli.build_app

#

the ExampleClientPlugin is just the plugin for the example itself, not the lightyear plugin

#

Can i see your protocol?

drifting prawn
#

Ahh yea, my client/server is both lightyear and app.

#

Let me add you, 1 second

#

Added.

#

Matching your ordering now.

#

And it works now!

#

So the order was lightyear -> protocol -> game client

pine cape
#

Awesome! and before, you had lightyear -> game client -> protocol?

drifting prawn
#

Yes

#

Well first was protocol -> lightyear -> client

#

which silently failed.

#

then the order you listed was a panic

pine cape
#

oh i remember why it panics

#

it's because I call register_required_components::<Client, MessageSender<M>>

#

but you already had a Client entity

#

so you're not allowed to register

#

ok I really need to document this clearly

#

thanks

drifting prawn
#

Haha, I'd be happy to help!

#

Me and my brother have been struggling for a while with this and previous versions.

#

I think, personally an example thats 1 crate might be the easiest to follow (template like) but I totally get why you have it split into common + example.

#

Just makes learning difficult

#

I'd be willing to help build something if you want.

pine cape
#

but it doesn't do anything besides the client connecting

#

it would be nice to have it showcase a simple replication

drifting prawn
#

Gotcha, im sorta thinking the avian3d/2d wwill probably be the most common setup people would want.

#

But yea, I did miss that simple example, so maybe I'm the dense one.

pine cape
#

aha no, i haven't really kept the docs up to date recently

drifting prawn
#

Fair enough! I know them feels.

pure drum
#

Neat little system to update the name of entities based on replicated/predicted/interpolated if you use egui inspect or similar with Name:

app.add_systems(Update, (
    on_entity_network_type_change_name::<Replicated>("replicated"),
    on_entity_network_type_change_name::<Interpolated>("interpolated"),
    on_entity_network_type_change_name::<Predicted>("predicted"),
));

fn on_entity_network_type_change_name<T: Component>(
    s: &str,
) -> impl FnMut(Query<&mut Name, (Changed<Name>, With<T>)>) {
    move |mut query: Query<&mut Name, (Changed<Name>, With<T>)>| {
        for mut name in &mut query {
            if name.contains(s) {
                continue;
            };

            *name = Name::new(format!("{} ({})", *name, s));
        }
    }
}
earnest rover
#

I just had a pause for 6 months, and trying to upgrade to bevy 0.16 and lightyear 0.20, and it seems EventReader<InputEvent<Inputs>> is not the way to go anymore on the server? It seems to be in a couple of readme files only

wraith stag
unkempt sedge
unkempt sedge
#

@pine cape sorry to bother but does lightyear have a state that tells me if my client is connected

pine cape
#

Yes, in the new version it's just With<Connected>

pine cape
pure drum
trim sable
#

Hello! I'm interested in using this crate for networking via p2p using steam relay servers. Is there some example that could be a good starting point for this? ๐Ÿ™‚
Not sure how much that would differ from some of the simple examples that I found on the repo.

pine cape
#

Hi, I haven't tested it but all examples should be compatible with steam.
You would need to create your own Steamworks client and pass it to the Steam IO

#

(Instead of the NetcodeClient + WebTransportIO used right now)

trim sable
#

Alright I see! When you say "create your own Steamworks client" are you referring to "lightyear_steam/src/client.rs" or something more custom I would need to do?

pine cape
#

You would need to create a steamworks client using the steamworks library.
I'm on my phone right now, but you should check out the docs and examples from the aeronet steam crate, which is what lightyear uses under the hood

trim sable
#

Will do. Thanks a lot ๐Ÿ™‚

drifting prawn
#

I was using .add_resource prior to 0.21, what is the new expected pattern for sharing a singleton? Local singletons + messaging? Single Entity?

pine cape
#

Just adding it as a component on an entity (which is what resources will become in bevy 0.17, if it can get done in time)

drifting prawn
#

Ahh, ok! thanks

#

0.21-rc.2 is working great btw!!

#

Can't say I ever got previous versions to work well.

#

The only issue I have atm is with pre spawned projectiles dissapearing, haven't began debugging, and don't really know how tbh

#

Other than that it's working great!

pine cape
#

nice! you could set RUST_LOG=info,lightyear_prediction::prespawn=trace to get additional logs

#

What issues did you encounter in previous versions?

drifting prawn
#

Constant rollback

#

And a bunch of weird despawn non existant errors, but upgrading seems to have gotten rid of all of it. Probably because I had to do quite a bit of refactoring with the new APIs

feral mirage
#

@pine cape what are the cases that you would want to modify the data on the Confirmed on the client side?

pine cape
# trim sable Will do. Thanks a lot ๐Ÿ™‚

In the main branch I updated the simple_box example to use steam as a relay network. You have to run with the steam feature, and uncomment out the steam parts in example/common/cli

pine cape
#

Btw I will be releasing the new version tonight

feral mirage
pine cape
#

Hm interesting, do you need that for something? I van do that for a future release, that's a pretty big change. But I thing it would work

feral mirage
#

it decreases the amount of Or<With<Predicted>, With<Interpolated>> since Confirmed would stop appearing on queries

pine cape
#

Hm let me think about it, that would be a win

feral mirage
#

keeping Confirmed public would still allow people to query confirmed entities if needed

unkempt sedge
#

Whenever sending a message_to_target, and I grab it in another client. I get the server client instead of the sender, is that expected behaviou? I am guessing that is due to server being the one actually sending the mesages

pine cape
#

Could you please clarify? Maybe with a short code snipper/example

unkempt sedge
#
fn challenge_button_react(
    mut query: Query<
        (&Interaction, &mut BorderColor, &CarrierId),
        (Changed<Interaction>, With<ChallengeButtonMarker>),
    >,
    challenge_ui: Query<Entity, With<ChallengeUiMarker>>,
    mut connection_manager: ResMut<ClientConnectionManager>,
    mut next_state: ResMut<NextState<ChallengeMenu>>,
    easy_client: Res<EasyClient>,
    mut commands: Commands,
) {
    for (interaction, mut border_color, carrier_id) in &mut query {
        match *interaction {
            Interaction::Pressed => {
                border_color.0 = Color::WHITE;
                // The field from is needed due to server becoming the from whenever sending message
                let _ = connection_manager.send_message_to_target::<CommonChannel, ChallengeSent>(
                    &ChallengeSent {
                        from: easy_client.client_id,
                        to: carrier_id.0,
                    },
                    NetworkTarget::Single(carrier_id.0),
                );
                if let Ok(challenge) = challenge_ui.single() {
                    commands.entity(challenge).despawn();
                    next_state.set(ChallengeMenu::Closed);
                    debug!("Sent challenge to {}", carrier_id.0);
                } else {
                    warn!(
                        "He skipped the menu before we could close it ourself. Little gap abuser e.e"
                    )
                }
            }
            Interaction::Hovered => {
                border_color.0 = Color::BLACK;
            }
            Interaction::None => {
                border_color.0 = Color::BLACK;
            }
        }
    }
}``` The receiveing client cant read the from field in the given event right? Because server is the one who sent him that
pine cape
#

I see, and the From field should be the original client who sent the message? Because the server was just rebroadcasting it?

trim sable
drifting prawn
#

Ok finally go around to getting the logs for the Prespawn entities that disappear, it looks like a hash mis-match. I am using Default for the component with it's default hashing. It's very odd to me that the hashes could mismatch, but only some times.

#

This is how I am spawning the entity (I truncated the let ability part, to one item, as it normally is a match statement per ability type, but this should be the static component shape both server and client see)

fn cast_ability(
    commands: &mut Commands,
    character: &CharacterQueryItem,
    e: &crate::abilities::AbilityEvent,
    charge_effect_query: &mut Query<(Entity, &AbilityChargeEffect)>,
) {
    let CharacterQueryItem {
        facing,
        tag,
        controlled_by,
        position,
        ..
    } = character;

    let spawn_location =
        Transform::from_translation(position.0 + (Vec3::Y * 0.35) + (facing.forward * 0.5));

    client_remove_ability_charge_effect(commands, charge_effect_query, e);

    /// removed large match statement to just one item for paste clarity
    let ability = commands.spawn((
        LinearVelocity(facing.forward * 250.0),
        TimeToLive { duration: 0.5 },
        Collider::sphere(0.075),
        Mass(0.5),
        SpawnEffect::new(e.ability_state),
        children![(
            Collider::sphere(0.105),
            HitEffects {
                effects: vec![HitEffect::Despawn],
            },
            HitBox {
                owner: Some(e.owner),
                ability_state: e.ability_state,
            },
        )],
    ))
    .id();

    // Insert shared components
    commands
        .entity(ability)
        .insert((spawn_location, RigidBody::Dynamic, PreSpawned::default()));

    // Check if server
    if controlled_by.is_some() {
        commands.entity(ability).insert((
            Replicate::to_clients(NetworkTarget::All),
            PredictionTarget::to_clients(NetworkTarget::Single(tag.client_id.0)),
            InterpolationTarget::to_clients(NetworkTarget::AllExceptSingle(tag.client_id.0)),
            *controlled_by.unwrap(),
        ));
    } else {
        // Let the server despawn the ent
        commands.entity(ability).remove::<TimeToLive>();
    }
}
#

I think one of the problems is I only want my TTL component to live on the server. How does PreSpawn generate the hash? I would assume it's quite common to want a different set of components on the server and client.

#

As a test I just set my own hash to the ability enum type as u64; Still get hash mis matches, which should be impossible.

pine cape
pine cape
drifting prawn
#

So it's the sum of networked components?

#

err, well not even

#

Could be more

#

I assume a static hash of say 0, should always work if I only spawn 1 entity shape, right?

#

But that seems to even fail

pine cape
# unkempt sedge yeah

I won't be supporting the old version of lightyear anymore, so you will have to migrate to get fixes; although one of the missing features in the new feature is the automatic rebroadcast of client->server messages to new clients

pine cape
#
  • the tick
drifting prawn
#

Gotcha, so using default the limit would be 1 entity per tick with that hash.

So given this scenario, manual hash of 0, and 1 entity or less per tick, should it always work?

pine cape
#

I don't understand what you're saying; if you manually set the hash on server/client, then the hashes will match yes. You can enable the trace logs to confirm that

drifting prawn
#

Yea, so thats what I'm trying and I still get a hash mist match

#

and the client despawns

pine cape
#

well what do the trace logs say

drifting prawn
#

client logs

2025-07-03T22:58:43.199937Z DEBUG lightyear_prediction::prespawn: found a client pre-spawned entity 4967v2#8589939559 corresponding to server pre-spawned entity 4985v1#4294972281! Spawning/finding a Predicted entity for it 0
2025-07-03T22:58:43.199952Z TRACE lightyear_prediction::prespawn: re-using existing entity
2025-07-03T22:58:43.199958Z DEBUG lightyear_prediction::prespawn: Added/Spawned the Predicted entity: 4967v2#8589939559 for the confirmed entity: 4985v1#4294972281 confirmed_tick=Tick(3291)
2025-07-03T22:58:45.666798Z DEBUG lightyear_prediction::prespawn: found a client pre-spawned entity 4968v3#12884906856 corresponding to server pre-spawned entity 4971v3#12884906859! Spawning/finding a Predicted entity for it 0
2025-07-03T22:58:45.666812Z TRACE lightyear_prediction::prespawn: re-using existing entity
2025-07-03T22:58:45.666816Z DEBUG lightyear_prediction::prespawn: Added/Spawned the Predicted entity: 4968v3#12884906856 for the confirmed entity: 4971v3#12884906859 confirmed_tick=Tick(3600)
2025-07-03T22:58:48.721333Z DEBUG lightyear_prediction::prespawn: Received a PreSpawned entity from the server with a hash that does not match any client entity server_hash=0
``` (took maybe 6 or so tries to get it to happen with a few seconds between each spawn
#

no logs from that system on server

#

rand with RUST_LOG=info,lightyear_prediction::prespawn=trace cargo run client

#

same env var for server

pine cape
#

THere's no log "PreSpawned hook, setting the hash on the component" on the server?

drifting prawn
#

No logs from the server, correct.

#

Only message is an internal bevy remove log for ReplicateLike

#

maybe useful?

pine cape
#

does your server crate contain lightyear_prediction?

#

or do you compile with no default features or something

drifting prawn
#

It's one binary

#

for server and client

pine cape
#

do you get those logs on the client?

drifting prawn
#

No

#

not on either

#

am I missing a plugin?

pine cape
#

ah that log doesn't trigger if you set a manual hash

drifting prawn
#

I am getting these logs from the server, but nothing else

2025-07-03T23:09:17.328343Z  WARN bevy_ecs::error::handler: Encountered an error in command `<bevy_ecs::system::commands::entity_command::remove<(lightyear_replication::hierarchy::ReplicateLike, lightyear_replication::hierarchy::RelationshipSync<bevy_ecs::hierarchy::ChildOf>)>::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 4807v4 was despawned by /home/zach/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/relationship/mod.rs:257:21
peak ice
#

just updated to 0.21.0, what happened to all the imports? IoConfig, ClientTransport, CompressionConfig etc.

#

oooh looks like theres been a lot of changes to the code, wow very nice

pine cape
pine cape
#

โšก Here is the release post! #crates message

peak ice
#

Do you have an example of creating a crossbeam channel for separate mode server & client yet? I might have missed it but the common create doesn't seem to handle it yet

pine cape
peak ice
#

ok sweet! thank you:) Also there might be a bug (or my misunderstanding) but I have a simple menu example I'm trying to update and the client will connect to the server when the client connect code is in schedule Startup, but not when I change it to OnEnter(MultiplayerState::Client)

pine cape
#

It's possible that some transitions don't work well if you're changing between mode dynamically; when you do OnEnter(MultiplayerState::Client) do you also trigger Connect on the client entity?

peak ice
#

so a button press changes my own personal state "MultiplayerState" from None to Client, and then the system that trigger_targets Connect is called OnEnter(MultiplayerState::Client)

#

Yes i am triggering Connect on the client entity, Not sure what you mean by changing modes dynamically, right now the server and client are running in different terminals with the use of a cli

peak ice
#

Server info

2025-07-04T16:02:35.592167Z  INFO lightyear_Menu_Example: Started Server as main task
2025-07-04T16:02:35.603624Z  INFO bevy_render::batching::gpu_preprocessing: GPU preprocessing is fully supported on this device.
2025-07-04T16:02:35.607616Z  INFO lightyear_udp::server: Server UDP socket bound to 127.0.0.1:5000
2025-07-04T16:02:44.472456Z  INFO lightyear_udp::server: Received UDP packet from new address: 127.0.0.1:4000
2025-07-04T16:02:44.472585Z  INFO lightyear_udp::server: Spawn new LinkOf entity=77v1#4294967373 server_entity=76v1#4294967372
2025-07-04T16:02:44.472728Z ERROR lightyear_udp::server: Received UDP packet for unknown entity: 77v1
2025-07-04T16:02:44.573806Z  INFO lightyear_udp::server: Received UDP packet from new address: 127.0.0.1:4000
2025-07-04T16:02:44.573972Z  INFO lightyear_udp::server: Spawn new LinkOf entity=78v1#4294967374 server_entity=76v1#4294967372
#

client info

2025-07-04T16:02:44.454673Z  INFO lightyear_netcode::client: client connecting to server 127.0.0.1:5000 [1/1]
2025-07-04T16:02:44.456029Z  INFO lightyear_udp: UDP socket bound to 127.0.0.1:4000
2025-07-04T16:02:47.519700Z  INFO lightyear_netcode::client: client connect failed. connection response timed out
2025-07-04T16:02:47.519847Z  INFO lightyear_netcode::client_plugin: Client Netcode(0) disconnected. State: ChallengeResponseTimedOut
feral mirage
#

which example uses the equivalent to the old ServerReceiveMessage?

feral mirage
#

@pine cape to use as a key to a map, should i use PeerId or RemoteId?

pine cape
pine cape
feral mirage
#

ok

#

FrameInterpolationPlugin not public?

pine cape
#

hm yeah it should probably be re-exported

#

you just have to use lightyear_frame_interpolation directly

feral mirage
#

it's what i did

pine cape
#

I found the race condition, but i'm not sure on what a good fix could be

feral mirage
#

@pine cape what is the new way to send message to a specific room?

#

ServerMultiMessageSender with Target::Single(PeerId::Entity(room))?

pine cape
#

there's no built in way; I guess just MultiMessageSender where senders = room.clients.keys()

feral mirage
#

hmm, ok

peak ice
pine cape
#

It's here: https://github.com/cBournhonesque/lightyear/blob/83cd575ddb96dbe7a24578e1375a003fe1ff3280/lightyear_udp/src/server.rs#L186
Frame 1:

  • receive packet 1: we insert an entry in the map, then we queue a command to spawn a Link
  • receive packet 2 from the same client: we check if a Link exists for that client. It doesn't because the command hasn't run yet, so we remove the entry.

Basically if we receive multiple connection packets in the same frame it causes some issues

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

peak ice
#

having the client spawned in Setup, and then triggering client connect later seems to work:)

pine cape
#

i think it was just luck :p

peak ice
#

it seems to be working consitantly if that helps you debug the race condition

feral mirage
#

@pine cape the old ServerReceiveMessage has a from() method to get the sending Peer, but MessageReceiver does not have any mechanism for that, is MessageReceiver the correct alternative to ServerReceiveMessage?

pine cape
#

you now have one link per connection; and the RemoteId on that link lets you identify who sent the message

feral mirage
#

so there are multiple MessageReceivers?

pine cape
#

on the server you will have one Link entity per connected client.
Each of these will have Link LinkOf MessageReceiver RemoteId, etc.

feral mirage
#

any mechanism for quickly identifying links that have messages? triggers maybe?

pine cape
#

Messages are similar to bevy Events, they are buffered and you have to check the buffer every frame

feral mirage
#

ok, that looks more like it

#

the MessageReceiver thing, each Link will have multiple, one for each kind of message right? so you would need to iterate over all links to check for messages right? and for each MessageReceiver

pine cape
#

and if you know you're on the client you can use Single for faster syntax

feral mirage
#

i saw that example, and because of that Single i thought that there was only going to be 1 MessageReceiver

#

but that is for the client

pine cape
#

yep, it's just because the client only has one Link entity

feral mirage
#

components like Connected, Disconnected only remain on the entity for 1 frame?

pine cape
#

nope, they stay there

unkempt sedge
#

@pine cape I think it would be cool if the simple setup example could actually connect more than one client haha

#

It is kinda redundant to only be able to connect 1 in a multyplayer crate

feral mirage
#

Is HostClient still achieved by having both client and server plugins on the same app?

pine cape
pine cape
pine cape
peak ice
#

So for the crossbeam channel, I create it in main and pass it into the client and server plugins through their plugin structs. Maybe I'm being dumb, but does anyone have any ideas on how to get it into a system to I can add it to the server entity? Previously on battle star galatica, I saved the crossbeam channel to a resource and thats how I accessed it in a system, but now its a component not a resource

#

One solution I can think of is make crossbeamIO derive clone, so i can put it into a resource, but that seems stuborn

#

Trying to get crossbeamIO from the plugin struct directly into an spawned entity doesn't work without clone either, So I'll send a pull request:)

peak ice
#

No rush @pine cape (or anyone else that wants a to use lightyear in seperate mode for a steam game), but I think I have the menu example working besides the crossbeam server & client link. I get a ConnectionRequestTimedOut error when trying to connect. Maybe I followed the test example wrong, but would love to have you take a look if/when you get the chance:)

to test it you would do cargo run -- full and then press play

pine cape
peak ice
#

its removed later:)

peak ice
#

so when you enter the Client state, depending on whether you are pressing play or joining a server it should use crossbeam or udp respectively

#

and then when pressing play I use a second pair a crossbeam connections (not crossbeamIO) to communicate when the server is ready to join, so there is not race conditiion between the server starting and you trying to connect

unkempt sedge
#

It seens we really going for that everything is a entity thing

unkempt sedge
tropic jackal
#

Is there an idiomatic way to get the ClientId from a ServerReceiveMessage? Trying to determine what to do based on the client that sent the message

pine cape
tropic jackal
#

Ahhhhhhh darn, thanks! I'm still on 0.19.1
I have one last dependency that only (officially) supports Bevy 0.15.3 max

pine cape
#

it's also present on 0.19 ๐Ÿ™‚

unkempt sedge
#

Ah client has a bunch of components associated to it

pine cape
unkempt sedge
#

Or is that done via the protocol

#

really cool, tho opens a lot of doors

pine cape
#

You can add it manually; or if you add a network direction for that message in your protocol, it basically does app.add_required_component::<Client, MessageSender<M>>()

tropic jackal
#

Is it a bad idea to do the following in the same system:

commands.entity(entity).remove::<ReplicationTarget>();
commands.entity(entity).despawn_recursive();
#

tokio poops itself and my client/server apps crash hard

#

I'm guessing one system to stop replication, and another to despawn the server-side entity with a Removed<ReplicationTarget> filter might be the better way

pine cape
#

you're trying to despawn locally without the despawn getting replicated?

#

I think you can do

commands.entity(entity).remove::<Replicating>();
commands.entity(entity).despawn_recursive();
tropic jackal
#

I'm trying to despawn on both local and remote, but I have sort of a chicken-and-egg problem where if I despawn both locally and remote, the server tries to replicate the despawn to something that no longer exists... so I have to make sure the server does its thing first

#

I probably messed something up but when I first tried despawn_recursive() on remote the entities (rendered meshes) still persisted on the client

pine cape
#

If you want to despawn both on local and remote, you should just despawn the entity on the server

#

the despawn will be replicated to the client

#

so just commands.entity(entity).despawn_recursive();

tropic jackal
#

Interesting, that is what I'm doing

#

I'm pretty sure I'm passing in the right entity. I'll do some more debugging

#

Gosh I don't know how many times I've done this... my bad haha. I'm pretty sure I'm not mapping the entities.

#

Always forget to do that

pine cape
#

you're sending the entities to despawn in a message?

#

yes it's kind of error prone; i wish i could make it easier to not forget this

tropic jackal
#

Yeah I'm probably going about most of the networking stuff wrong.

In this scenario I'm sending just a simple newtype message to the server that contains an Entity. The server will despawn that entity and spawn a new one in its place.

My whole mental model of how I'm going to do networking stuff is basically:

  1. Client sends message to server
  2. Server updates if message is valid
  3. Server updates get replicated to all clients

I'm trying not to transfer authority to clients, not because I don't think its a good idea but because I don't understand it or know if it's a better way of doing things ๐Ÿ˜…

pine cape
#

What kind of game are you making? You can just spawn entities on the server with server-authority

tropic jackal
#

A turn-based strategy game. Have you ever played Civilization before? I can give a concrete example of what I'm doing now

pine cape
#

Yes i have

peak ice
#

I would def just spawn on the server for a turn based game:) makes things simpler from my perspective

#

*not an expert though, but if you're not worried about real time gameplay (like prespawning bullets) just do as much as you can on the server

tropic jackal
#

Yep I am spawning everything on the server already, but the essence for me is when does the server know to spawn something?

So I have a settler on my screen (client) and I want to press a button, "Settle City". This event should be forwarded to the server somehow so it can found a city in the settler's place. I am forwarding this information to the server via a Message.

#

Server despawns settler, spawns a city in lieu of it, and replicates the new city to all clients.

pine cape
#

Client -> Server actions should be sent as part of Inputs.

#

Inputs must implement MapEntities, and any entity stored there will be mapped correctly to the server entity

peak ice
#

was just about to recommend using inputs

tropic jackal
#

Okie dokie, I'll make the switch. Thanks ๐Ÿ™‚
I've been confused about this before

pine cape
#

also inputs pressed on the client on tick T are guaranteed to be processed on the server on tick T

tropic jackal
#

Well, I know what I'm working on tomorrow!

pine cape
#

let me know how it goes

peak ice
#

So I think you could probably make custom inputs that aren't button presses or mouse positions, like maybe a enum of different actions like "settlecity", you'd just have to add them before the client sends the inputs. I think I did that somewhere in my 0.19 lightyear game if you need more details lemme know:)

tropic jackal
#

Yes thatโ€™s definitely what Iโ€™m thinking about doing now. Basically just defining a server API for clientsโ€ฆ

Iโ€™ve been having a ton of fun with #1357045538884816966 too. Planning on exposing most if not all of this API to scripts.

sterile needle
#

Loving the library! Btw it looks like the examples are slightly wrong, or at least they confused me for a bit. I guess the component tuple in Trigger is an "or" rather than an "and" (https://docs.rs/bevy/latest/bevy/prelude/struct.Trigger.html#method.components) so if I want an observer to only run once I need to do something like this:

    trigger: Trigger<OnAdd, PlayerId>,
    should_trigger: Query<(), (With<PlayerId>, Or<(With<Predicted>, With<Replicating>, With<Interpolated>)>)>,
    mut commands: Commands,
) {
    if !should_trigger.contains(trigger.target()) {
        return;
    }

Is there a better way of filtering to the "correct" entity? I also tried Without<Confirmed> but it still gets run multiple times in the client.

#

For context, I was trying to figure out why I was getting multiple players on the client before understanding what was happening. The simple_box example handles it by having Without<Confirmed>> in the draw_boxes system query, but things like handle_predicted_spawn are still getting run two or three times

pine cape
#

Triggers are indeed an (OR), but they only run once for the list of components if an entity contains multiple of them.
(i.e. if you have Trigger<OnAdd, (A, B)> and both (A, B) are added at the same time on the entity, the observer triggers only once)

sterile needle
#

Oh interesting, thanks that's a good distinction

pine cape
#

The reason why some of these observers conditions are complicated is because I try some edge-cases related to HostClient, for which I don't have a great solution for. The main thing i'm missing to improve things is being able to order observers.
If you don't use HostClient, you can just use something like

pub(crate) fn handle_interpolated_spawn(
    trigger: Trigger<OnAdd, PlayerColor>,
    mut interpolated: Query<&mut PlayerColor, With<Interpolated>>,
)
#

it's true that it will trigger 3 times (one for Confirmed, Predicted, and Interpolated), but unless you're replicating a lot of entities it shouldn't matter

#

if you are, you can just switch to using a system instead of an observer

sterile needle
#

Yeah, it shouldn't matter but I had my setup_player observer creating a new rigid body, so it ended up looking like two separate player entities ๐Ÿคฆ

#

Definitely my mistake lol

#

My suggestion is more for documentation, adding a comment explaining the observer conditions to the simple_box example might save someone else some time. It's possible it's already explained in the book and I glossed over it

pine cape
#

Thanks for the feeback, which part should be explained further? feel free to open a PR to improve the examples!

sterile needle
#

Specifically that handle_predicted_spawn will get triggered for Confirmed, Predicted, and Interpolated. Sure, I'll send a PR with some additional comments

unkempt sedge
tropic jackal
#

Might be a silly question, but I'm reading through the book and saw where a Gizmo is spawned on the server and replicated. Do people typically spawn their meshes server-side this way and just have them get replicated to the client? If so, even if the server is headless?

pine cape
#

no i think the meshes are only spawned on the client

tropic jackal
tropic jackal
#

Moving over to Inputs now. If there is no entity with an ActionState<I> component in the world, what is that indicative of?

#

InputPlugin::<MyInputs>::default() is registered.

#

Trying to buffer them on the client but single_mut for the component fails

pine cape
#

This will insert an ActionState<I> for it

#

that part is a bit unclear, i admit

tropic jackal
#

Ok thanks, I am a bit confused about the whole Inputs paradigm in that sense. So this "entity that I want to control" should be a singleton? I'm inferring this because of the get_single_mut() in the example, but perhaps that's just in the context of the simple box example where I'm assuming there is literally only one entity to control.

For my use case I'm trying to tether inputs to elements in the UI. Would I want to add an InputMarker<MyInputs> to each UI element capable of processing inputs?

peak ice
#

I'd probably just have one entity, with like a player component, player score compoent, etc. And also have the inputs there all in one place

#

and then when you press a button, you can set the action_state with the input you want, kinda like here I have a function for mouse position:


pub fn global_mouse_input(
    windows: Query<&Window>,
    query_view: Query<(&Camera, &GlobalTransform), (With<Camera2d>, With<OuterCamera>)>,
    // mouse: Res<ButtonInput<MouseButton>>,
    mut action_state_query: Query<
        &mut ActionState<PlayerActions>,
        (With<Predicted>, With<Controlled>),
    >,
) {
    let window = windows.single();
    let (camera, view) = query_view.single().expect("no camera view?");

    if let Some(world_position) = window.expect("no window")
        .cursor_position()
        .and_then(|cursor| Some(camera.viewport_to_world(view, cursor)))
        .map(|ray| ray.unwrap().origin.truncate())
    {
        for mut action_state in action_state_query.iter_mut() {

            action_state.set_axis_pair(&PlayerActions::MousePos, world_position);
        }
    }
}
#
app.add_systems(
    FixedPreUpdate,
    global_mouse_input
        .before(InputSystemSet::BufferClientInputs)
        .in_set(InputManagerSystem::ManualControl),
);
tropic jackal
#

Right exaclty, I understand that. That's what I'm going for. My question is really one about architecture... it's just not really intuitive to me why it's a singleton. It just kind of is in the example/book

peak ice
#

hmmmm, I'm not sure I have a anwser to your question then, I've always just had one ActionState per player. I think it helps you know which player the actions are from, not sure if under the hood lighyear can have more than one actionstate per player

pine cape
#

You could have 2 entities, and WASD controls entity 1, and the UpLeftDownRight controls entity 2

#

I don't really have support for 'global' inputs that are not attached to an entity right now. My plan is that for those you would attach the InputMarker to the Client entity iself

#

but for now I think you just have to create a mock entity that will hold your inputs

#

if you had multiple entities to control

tropic jackal
#

Got it, thanks! And one last question:
I'm making this entity locally? The server will pick it up somehow?

I already have Player entities that are spawned on the server and replicated to all clients. Should I insert the InputMarker<MyInputs> component remotely or on the locally relicated entities? Simple box seems to do it client-side

pine cape
#

Good question, you can try it but I don't think it will work because the server doesn't know about the entity.
The entity should be spawned by the server and replicated to your client

#

and you add InputMarker on the local entity (Replicated or Predicted) on the client that you want to control

tropic jackal
#

Okie dokie, thanks ๐Ÿ™‚ I'll trudge on
Thanks for being so responsive, both you and @peak ice

peak ice
#

has anyone gotten steam to work for p2p connections in 0.21.0?

pine cape
peak ice
#

i might have some questions soon because I'm running steam in separate mode:)

#

But this might also be a problem for next weekend

unkempt sedge
#

oh servermultimessage sender is gonna be useful

peak ice
#

So previously, I had steam in this weird Arc parking_lot to get it to both the server and client in separate mode

let steam_client: Arc<parking_lot::lock_api::RwLock<parking_lot::RawRwLock, SteamworksClient>> = Arc::new(RwLock::new(SteamworksClient::new_with_app_id(480).unwrap()));

Now, we have

let (steam, single_client) = lightyear::prelude::steamworks::SingleClient::init_app(480);

I believe I want both the client and server to process steam callbacks when hosting, but single_client needs to be added as a non-send resource so I can't send the server app to a separate thread as of right now.

Any ideas on how to tackle this problem?

#

steams handled by areonet now right? Should I contact that person about it?

pine cape
#

Maybe you can just wrap single_client in a fake 'send' resource?

#

i don't think aeronet_steam actually uses single_client

peak ice
#

So I've tried just moving steam and not single_client, but I think this code is required to get steam server to accept p2p request.

.insert_non_send_resource(steam_single)
.add_systems(
    PreUpdate,
    |steam: NonSend<lightyear::prelude::steamworks::SingleClient>| {
        steam.run_callbacks();
    },

You're recommending putting steam_client into a arc<mutex<>>> and then after the move pulling it out and appyling the above code? and then pray it works?

pine cape
#

yes, i think that the single_client doesn't actually required 'Send'

peak ice
#

sweet, thank you:)

peak ice
#

wait actually I'm a little confused, do i need to change lightyear back to SyncCell? or is there a way around it with the use of SyncCell? Can we have single_client derive clone?

#

ooooh single_client is defined in steamworks not lightyear

pine cape
#

yes, you have to supply your own steamworks client

#

so you can do whatever you want with it

peak ice
#

what do you mean by supply my own steamworks client? like fork steamworks and change single_client to clone?

pine cape
#

no i mean that lightyear doesn't create it for you

#

you create it yourself using steamworks rs

peak ice
#

sorry for the many questions, feeling very confused about what changed. So I import steamworks in my toml file, init steamworks, but then I'm still stuck with a singleClient I can't clone or move without a mutex

#

Sorry I know this isn't exactly lightyear related, more of a me issue with rust lol

pine cape
#

I think the steam.run_callbacks() system needs to run in only one of the apps

#

it's basically just something that needs to run regularly

#

so you might not even need to send it another app or clone it or anything

peak ice
#

ok:) thanks for the help and encouragement, I'll make sure to try having it only run on one of them:)

peak ice
#

alrighty I think its working!! I got a quick and easy question for ya now:) Encountered an error in observer lightyear_udp::server::ServerUdpPlugin::link: Address already in use (os error 48), Does stop server trigger not close the Udp?

#

this bug happens if I press Play, press quit (which disconnects me and stops the server), and then press play again

pine cape
#

nope, Stop server stops the netcode connection, but not the underlying IO

#

to stop the IO, you would have to trigger Unlink

peak ice
#

Sick

#

Ur the best!

pine cape
#

But just triggering Start/Stop should work with Udp, that's what i do in the examples

#

I always keep the Udp io open

peak ice
#

I can't seem to find where you stop the server and start it again in the examples

pine cape
peak ice
#

thank you:) very curious as to why its not working for me, do you get these warning. when you stop the server?

2025-07-06T21:34:27.389732Z  INFO lightyear_Menu_Example::networking::server: Server received StopServer command
2025-07-06T21:34:27.389754Z  INFO lightyear_Menu_Example::networking::server: Server Stopped
2025-07-06T21:34:27.389774Z  INFO lightyear_netcode::server_plugin: Stopping netcode server
2025-07-06T21:34:27.390041Z  WARN bevy_ecs::error::handler: Encountered an error in command `<bevy_ecs::system::commands::entity_command::despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID PLACEHOLDER does not exist (enable `track_location` feature for more details)
    
2025-07-06T21:34:27.409763Z  INFO lightyear_netcode::server_plugin: Disconnection from netcode client 1. Despawning entity.
2025-07-06T21:34:27.409799Z  WARN bevy_ecs::error::handler: Encountered an error in command `<bevy_ecs::system::commands::entity_command::despawn::{{closure}} as bevy_ecs::error::command_handling::CommandWithEntity<core::result::Result<(), bevy_ecs::world::error::EntityMutableFetchError>>>::with_entity::{{closure}}`: The entity with ID 98v1 does not exist (enable `track_location` feature for more details)
    
2025-07-06T21:34:30.380036Z  INFO lightyear_netcode::client: client connection timed out
2025-07-06T21:34:30.380082Z  INFO lightyear_netcode::client_plugin: Client Netcode(1) disconnected. State: ConnectionTimedOut
pine cape
#

no, haven't seen that

peak ice
pine cape
#

thanks for making this btw! i haven't tried to get separate mode working, so it's great to see that it works

pine cape
#

I've updated the rollback/correction logic following some tips from Joy and now it looks much much smoother than before!

#

Now doing deterministic-replication like GGPO where we only replicate inputs seems feasible, since the prediction/correction works very well

#

@wintry dome also when predicting other clients, i now fetch by default the latest input available in their input buffer, so no need to do the complicated logic from spaceships anymore

#

i'd really like to understand what's going on with the blinking/disappearing gizmos, though

pine cape
#

yep!

pure drum
#

Mad ๐Ÿ™‚

hot cloud
feral mirage
#

@pine cape what is the alternative for ServerReplicate now?

feral mirage
#

@pine cape on the client, which component marks the singleton? LinkOf?

pine cape
#

Client

#

LinkOf means 'Link' that is a child of the Server

feral mirage
#

is there a better way?

network_state: Single<
        (
            Has<Connected>,
            Has<Connecting>,
            Has<Disconnected>,
            Has<Disconnecting>,
        ),
        With<Client>,
    >,
inner hill
#

is the rollback using a custom schedule?

#

i added some stuff to the gizmos a while ago to try to remove flickering in FixedUpdate, but if its using something outside of that, you might want the same solution for your own

pine cape
#

The rollback is in PreUpdate, and it manually runs FixedMain several times. Otherwise the gizmos are being updated in PostUpdate.

pine cape
inner hill
#

@pine cape is the issue happening on main, or just on that branch?

#

nvm i see its on main rn

molten kraken
#

is there a way to not replicate a specific component on an entity to a single client, and replicate it to the rest normally?

pine cape
unkempt sedge
#

@pine cape If you had a crated project, do you think it would be wise to have a central state manager, or modularize each state for each crate. For example: Save state runs in crate save, and completely ignores asset state in crate asset

pine cape
#

What do you mean by state, a bevy State?

feral mirage
#

Yappy rather have each sub crate control when they are initialized

pine cape
#

I'd probably start with a single ServerStates, seems simpler

unkempt sedge
#

hahaha jk

pure drum
#

I'm getting a strange bug, i think related to bei trigger handling. (0.21.0 and main)

2025-07-08T21:33:11.306335Z  WARN aeronet_io::packet: 538v1 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer

I get a client but not a connection.

This happens when i don't have an observer running on a Fired trigger, e.g adding this trigger observer removes that warning and the client connects just fine:

app.add_observer(impulse_jetpack_client);

fn impulse_jetpack_client(
    trigger: Trigger<Fired<EvaMove>>,
) {}

I have this on startup:

    app.add_plugins(InputPlugin::<EvaContext> {
        config: InputConfig {
            rebroadcast_inputs: true,
            ..default()
        },
    });
    app.register_input_action::<EvaMove>();
pure drum
#

Thinking about this.. will Trigger work with rollback since it won't be called in FixedUpdate? Or is BEI calling the Trigger itself?

inner hill
#

aren't triggers called regardless of how the event was activated

pine cape
#

Bei will run during rollback and will resubmit the trigger

pine cape
pine cape
#

I have an example called bevy enhanced inputs if you want an easy starting point

pure drum
#

Yeah I'm using that! Works perfectly so far ๐Ÿ™‚

#

Can't reproduce it.. I'll leave it for now. I must be doing something strange. ๐Ÿ™ˆ

pine cape
#

I get a client but not a connection
What do you mean by this? This has to be related.
Lightyear does not support not using Netcode right now

pure drum
#

Not a big deal! it works fine with enabling the hook ๐Ÿ˜„

#

Those "Recomputing" logs also happens when it connects properly

pine cape
#

I need to fix those Recomputing input delay logs, they are annoying lol

#

I don't get the issue in your logs, I don't see

2025-07-08T21:33:11.306335Z  WARN aeronet_io::packet: 538v1 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer
pure drum
#

Thanks for investigating anyway. If I see it again with some other code, I'll check it out again and try a proper strip down of my project to replicate it.

feral mirage
#

@pine cape do you know Path of Exile? they have on the client a toggle to choose between Prediction and Lock Step, Lock Step is basically Confirmed only, does lightyear has anything like this?

pine cape
#

I don't think lock step is Confirmed only. I think lock step is setting the input delay to the rtt of the player, to avoid doing any prediction
You can do this in lightyear by updating the InputConfig

unkempt sedge
#

i ask bcs right now i have a shouldbe predicted entity but no predicted entity womp womp

slow kernel
pine cape
unkempt sedge
#

now i have crazy bug just lovely

feral mirage
pine cape
#

Yeah that's definitely lockstep with input delay

#

I'm working on adding deterministic replication, including lockstep

feral mirage
#

@pine cape so, we are getting 2025-07-09T21:17:00.187598Z ERROR lightyear_replication::send::components: ClientOf 153v1#4294967449 not found or does not have ReplicationSender, but looking at the inspector the entity does exist and have ReplicationSender, BUT it might be because we are adding things on a system with Trigger<OnAdd, Connected> and those commands are run before the component is properly added to the component, do you know anything about that?

pine cape
#

The trigger should be OnAdd, Libked

#

Linked

#

To avoid these race conditions

pure drum
#
2025-07-10T01:35:50.584053Z DEBUG lightyear_prediction::registry: check history_value=Some(Updated(Position(Vec2(0.0, 0.0)))) confirmed_component=Some(Position(Vec2(-4.3987303, 1.9698288)))
2025-07-10T01:35:50.584070Z DEBUG lightyear_prediction::registry: Should Rollback! Confirmed value is different from history value

Thanks for adding this! Very helpful ๐Ÿ™‚

molten kraken
#

is there support for automatically syncing events across the network?

pine cape
#

events that are not triggers?
you would have to register them as messages and then handle the message->event-buffer transition

feral mirage
#

@pine cape there is no tag for 0.21.1

feral mirage
#

i'm getting 2025-07-10T14:10:04.519882Z ERROR lightyear_transport::plugin: error sending message: ChannelNotFound, is anything other than add_channel needed?

dry flower
feral mirage
pine cape
#

Adding the direction

pine cape
# dry flower Hi ๐Ÿ‘‹ just put a PR up to fix the RemoteTimeline syncing https://github.com/cBou...

Thanks that's awesome, I'll take a look!

  1. It is possible there is a problem because remote timeline also upgrades based on FixedTime progressing, however it is still also updated from receiving remote packets so it should balance out. I would be interested in seeing your test cases where you can manually create an issue
  2. Honestly I just switched to offsets because Claude recommended it for added precision. It is possible that it would be equivalent to just updating the time directly, I would have to think about it.

Empirically when I enable trace logs things do seem to work (if we're ahead we slow down and get back in sync)

#

I would love to have some unit tests/integration tests for this but they are hard to write

unkempt sedge
#

(also fixed typo in hierarchy.rs that broke compilation) @dry flower chad

dry flower
dry flower
#

ah-ha, yep

let small_delta = TickDelta::new(0, Overstep::from_f32(0.5), false);
assert_eq!(TickDelta::from_i16(0) + (-small_delta), -small_delta);

assertion `left == right` failed
  left: 65535:0.50
 right: -0:0.50

Left:  65535:0.50
Right: -0:0.50
#

my 2c, I think an implementation based on unsigned deltas + a neg: bool is going to be hard (and ugly) to get right. I assume your reason for splitting that type into tick_diff and overstep (rather than use some floating-point type for the whole thing) is so that large TickDeltas don't have worse resolution, which is reasonable, but I would at minimum make tick_diff an i32 and get rid of neg, I think that would make the math functions a lot simpler to implement

#

or better yet, you could use something like a 48.16 fixed point (48 bits of tick_diff, 16 bits of overstep)

#

assuming my math is right, at 64tps that would give us a resolution of 238ns for the overstep and a range of +- 69,684 years. And basic arithmetic operations are a single cycle which is hard not to like.

pine cape
#

I just split it into tick_diff and overstep since it seemed more natural to work with, since the time is primarily expressed in terms of tick.
Also i wanted to reduce the data transmitted by eventually switching the overstep to 8 or 16 bits

dry flower
#

oh I see, PositiveTickDelta goes in some network messages

pine cape
#

Only the Pong, so admittedly it doesn't really matter

#

i'd be open to any of the changes you suggested to simplify this code!

dry flower
#

I'm with you on the 16 bit overstep (8 is maybe a bit coarse) and I was gonna say 16 bits seems a bit small for the tick_diff, but if you expect the value to always be small, that seems fine

pine cape
#

The tick_diff can go up to u16::MAX

dry flower
#

right, if tick_diff stays 16 bits then RemoteTimeline needs to set self.now at startup rather than self.offset

#

because self.offset (which is a TickDiff) would overflow if a new client joined a server that had been up for more than (2^16/64/60) ~= 17 minutes

dry flower
pine cape
#

ticks wrap around at u16::Max, so it should be ok no?

dry flower
#

oh I get what wrapping_id is now ๐Ÿคฆโ€โ™‚๏ธ

#

yeah that seems fine, there's no reason ticks would have to be monotonically increasing with no wraparound

#

I'd be curious to see how all of these clock sync algorithms hold up around that 17 minute mark though

#

just need to make sure it's handled that way consistently everywhere

pine cape
#

But yeah it's a great point that the RemoteTimeline is currently updated from how fast Time<Virtual> is going (the offset is an offset compared to the current timeline tick, which is incremented every FixedUpdate) which causes some weird effects

dry flower
#

Yeah, like I said I was really struggling to get a repro now so the issue was probably 95% TickDelta math, but I still think Time<Virtual> is probably wrong

#

My claude was very insistent about that and managed to convince me hahaha

pine cape
#

Haha; well my claude is the one who suggested the offset

dry flower
#

I reckon what we actually want is to advance our estimate using an up-to-date real-time clock at the start of every update()

#

say you get a FixedUpdate (advance_remote_timeline) on tick 100 and then late into that tick, like 0.9 overstep, you handle a packet from the server and calculate a new_estimate (accounting for network delay) that says the server is at 100.9

#

At that point you're perfectly in sync, you don't want it to look like your error is 0.9, but I think with the current system your now would still be set to 100.0 until the FixedUpdate a couple ms later

pine cape
#

I reckon what we actually want is to advance our estimate using an up-to-date real-time clock at the start of every update()
agreed

#

All received packets are handled in PreUpdate, before any FixedUpdate runs

#

Another thing is that the packets currently only contain the Tick at which they were sent.
For better precision maybe we should include the overstep. However I'm not sure how helpful the overstep actually is; the time is updated at PreUpdate so the overstep mostly depends on the PreUpdate time, not the real time at which the packet is actually sent in PostUpdate.

dry flower
#

Yeah, I think including the overstep could be nice, but agree that we'd have to always get the most up-to-date time. I'm new to all this so I definitely need to review how bevy's clocks work

pine cape
#

All received packets are handled in PreUpdate, before any FixedUpdate runs
This is probably another thing that's not great. The server sends packets in PostUpdate after the FixedUpdate has run and the ticks have been incremented. But the clients handles the packets in PreUpdate before the FixedUpdate has run, so any tick comparisons might be skewed

dry flower
#

The client seems to like to hover around a tick and a half ahead so that could very well be the reason why

#

that's not a bad thing according to that overwatch talk, or was it the rocket league one, idr

#

but probably better to control that explicitly instead of it being a happy accident

pine cape
#

The client is a tick and a half ahead after accounting for RTT, you mean?

dry flower
#

yeah I guess what I mean by that is that when looking at the server gui I feel like I see synced up clients having a tendency to flicker between 1 and 2 frames buffered

#

I think that's what the number next to client nametags means right

pine cape
dry flower
#

oh right, the 1 tick

pine cape
#

so the 1-2 ticks is just because i take stddev + 1 as margin

dry flower
#

yeah i've been staring at the remotetimeline so much that I forgot about the inputtimeline

#

Anyway I'll play around with using something like a 16.16 fixed point for tick+overstep because I feel like that would simplify a lot of code

pine cape
peak ice
#

is there a way to delete In use client ID's on the server once it is stopped?

#

I am getting this warning WARN lightyear_netcode::error: Netcode error: ClientIdInUse(Netcode(1)), but then eventually it says the client disconnected, and then it lets me connect again. (crossbeam separate mode stuff)

pine cape
peak ice
#

oooh, could the client which is reusing the same crossbeam channel be the issue? Like does a client link store data that would make the server think its the old connection? Or is this all server side?

#

oooh actually I messed up

#

I had two server side crossbeam LinkOf

#

for some reason I have to respawn the crossbeam server link entity every restart, but it seems to be working without warning now

feral mirage
#

@pine cape on the server i am using action_state.just_pressed(Action), but since 0.21 it rarely is true, if i spam i get some frames where it is true

#

and the state changes to JustReleased even when you are holding the button

pine cape
feral mirage
#

0.21.1

unkempt sedge
feral mirage
#
commands.spawn((
  Room,
  RoomOfEntities::default(),
  RoomOfClients::default(),
));

?

tranquil granite
#

Hey, are the examples in the github repo for the latest version 0.21?
Because I've been trying to get it to work for quite sometime and I cant understand whats wrong. I tried looking at the book for help and while its good, only helps in creating a window.

If someone could point me to a good learning resource that would be very helpful. Thanks

pine cape
#

The example in the github repo are for the main branch, which is very similar to version 0.21
What part doesn't work? I've tested the examples, they do work

tranquil granite
#

im trying to follow the simple_box project.

There are imports from outside the simple_box project folder so im a little confused.

I was able to understand the errors I had earlier however I have import errors now.

  1. failed to resolve: use of unresolved module or unlinked crate lightyear_examples_common
    use lightyear_examples_common::shared::FIXED_TIMESTEP_HZ;
  2. failed to resolve: use of unresolved module or unlinked crate lightyear_examples_common
    use lightyear_examples_common::cli::{Cli, Mode};
  3. failed to resolve: use of unresolved module or unlinked crate lightyear_examples_common
    use lightyear_examples_common::shared::SharedSettings;

The lightyear/examples/common folder has cli and shared
for shared its a pretty easy fix to just copy them but the cli file has other imports from within that folder so cant copy it...

pine cape
tranquil granite
tranquil granite
pine cape
tranquil granite
#

Thank you ๐Ÿ˜„

pine cape
#

done

pure drum
# pine cape I don't get the issue in your logs, I don't see ``` 2025-07-08T21:33:11.306335Z ...

This strange one has come back, but now if I add another observer to Trigger<Fired<X>> (bei) that error comes back on the server and the client times out connecting. i.e. this works:

pub(super) fn plugin(app: &mut App) {
    app.add_observer(|trigger: Trigger<Fired<EvaMove>>| {});
}

And this doesn't

pub(super) fn plugin(app: &mut App) {
    app.add_observer(|trigger: Trigger<Fired<EvaMove>>| {});
    app.add_observer(|trigger: Trigger<Fired<EvaMove>>| {});
}

Then this does work:

#[derive(Event)]
struct TestTrigger;

pub(super) fn plugin(app: &mut App) {
    app.add_observer(|trigger: Trigger<Fired<EscapeCurrentView>>| {});
    app.add_observer(|trigger: Trigger<Fired<EscapeCurrentView>>| {});
    app.add_observer(|trigger: Trigger<TestTrigger>| {});

This is doing my head in lol.

I can't replicate the problem in the bei example (main branch). Also different combinations of observers cause it and others don't. It's very strange.

I guess it's time to dig in ๐Ÿ™ƒ

pure drum
#

commenting out all app.add_plugins(InputPlugin::<EvaContext>.... and app.register_input_action::<..> doesn't make a difference either. Maybe not even related to input.

pine cape
#

You mean that you still get errors like

2025-07-08T21:33:11.306335Z  WARN aeronet_io::packet: 538v1 has 2 received packets which have not been consumed - this indicates a bug in code above the IO layer

?

pure drum
#

yeah

pure drum
#

I THINK this is it based on not getting any of these errors and trying a bunch of things.

  • I was loading the EnhancedInputPlugin along with a add_input_context to a context that isn't used in multiplayer, which is run before any (lightyear) InputPlugins.
  • If I comment out the EIP plugin and move the add_input_contexts to AFTER InputPlugin contexts are loaded, it has so far worked 100% of the time regardless of the combination of add_observers

I can't really see why this would be a problem since InputPlugin does load EnhancedInputPlugin then add_input_context anyway. Maybe system ordering, but again, it is set up after the EIP plugin.

#

Still can't replicate it on a lightyear example ๐Ÿซ 

#

Alright, so far the current solution for my setup, not knowing why: Don't manually load the EnhancedInputPlugin yourself.

peak ice
feral mirage
#

@pine cape rooms are supposed to be a way to filter visible entities right? i have entities that are in other rooms being visible

pine cape
#

Maybe you're using incorrectly? Take a look at the network visibility example

feral mirage
#

NetworkVisibility component

feral mirage
#

then RoomEvent::AddEntity would be actually Trigger<OnInsert, EntityInRoom>

#

or is there cases where an entity is in multiple rooms?

pine cape
#

Maybe; is it because you would want to inspect the EntityInRoom component?

dull lion
drifting prawn
dull lion
#

Hmm it seems that ReplicateLike is added automatically to all children of a replicated entity. I'm able to get rid of the warning by adding DisableReplicateHierarchy

feral mirage
pine cape
feral mirage
#

sadge

silent patrol
# pine cape Adding the direction

Took me a while to realise that it's channels that also need a specified direction, not just messages
would it make sense to add a must_use hint for the add_channel method?

pine cape
#

The thing is that it's not really must_use, you can also just add a channel sender/receiver to a transport independently from it being a client or server.

But I agree that this is error-prone

#

Most of the examples use a Client Server topology but I hope to support P2P in the future

#

I can update the ChannelNotFound error message to say 'did you forget to call add_direction?'

silent patrol
#

That would be great as well, thanks!

silent patrol
#

How can I setup interpolation and prediction for immutable components?
Seems like it's supposed to be supported in 0.21: https://github.com/cBournhonesque/lightyear/pull/1015

but the add_prediction and add_interpolation methods are still unavailable for immutable components, since the SyncComponent: Component<Mutability = Mutable> bound still expects mutable components

am I missing some other way to register components for prediction and interpolation?

pine cape
#

It is not possible right now, that PR only enables immutable components for replication

#

That's one of the main reasons why RelationshipSync exists

#

Instead of simply registering the relationship component directly

#

You need to add prediction/interpolation for an immutable component with mode Once or Simple?

unkempt sedge
#

@pine cape Why does component that get synced once need the synccomponent? :>

pine cape
#

What part of SyncComponent is the issue, PartialEq?

unkempt sedge
#

i get the error now

#

It warns me, it doesnt implement SyncComponent. When in reality it didnt implement clone

silent patrol
#

though I can see needing Simple as well in theory

feral mirage
#

is there a quick way to map from PeerId to the entity already built-in?

pine cape
silent patrol
unique plover
#

Unless it changed since I asked a couple weeks ago.

pine cape
#

Sorry, that's right! I'm on my phone so couldn't verify

pine cape
wicked oak
#

@pine cape are you using some api to make the connection stuff ? iam trying make my own and iam using Tokio @pine cape

pine cape
#

What connection stuff? the io layer?

wicked oak
pine cape
#

I just use the standard library udp sockets

pine cape
#

@silent patrol I have a commit to allow immutable components to be predicted/interpolated. Will push it tomorrow

stray sinew
#
thread 'Compute Task Pool (3)' panicked at /Users/rherv/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/721fbe6/lightyear_prediction/src/rollback.rs:439:13:
attempt to add with overflow

Got this error in the latest commit

pine cape
#

Oh.. should be (tick + 1000).0

feral mirage
#

@pine cape what is the way to register a immutable component?

#

more precisely, add_predition and add_interpolation

#

is the content of the immutable component always replicated to the Predicted/Interpolated entity from the Confirmed?

pine cape
#

It will be add_immutable_prediction for now

#

But I think I'll rework that api to be more strongly typed

feral mirage
#

hmm

unkempt sedge
#

My use case is I want the related entities to be replicated to the same rooms as the origin, but not follow the exact replication type

pine cape
#

What kind of replication behaviour do you want to override on the child

#

You should be able to just add ReplicateLike on the child

#

I think it's added automatically

unkempt ledge
# pine cape Oh.. should be (tick + 1000).0

I think this could overflow, too! you'd probably want tick.saturating_add(1_000).0 if you just want a high tick

...though this still wouldn't work right if tick is near u16::MAX; it might be better to introduce a more consistent mechanism for mismatches - maybe with a TickInstant..? buuut. that depends on your intended use of Overstep

unkempt sedge
#

Another question:

fn handle_weapon_carrier(
    trigger: Trigger<OnAdd, WeaponCarrier>,
    query: Query<&Predicted>,
    mut commands: Commands,
) {
    let entity = trigger.target();

    if query.get(entity).is_ok() {
        commands
            .entity(entity)
            .insert(WeaponInputs::default_input_map());
    }
}```
Why triggers like this dont work? It feels like I still need to use added systems
stray sinew
#

Might be a race condition. My program got a lot smoother after I replaced observers with Added filters

pine cape
pine cape
pine cape
#

I was thinking of switching from add_prediction(prediction_mode) to

add_prediction_full()

This would give us a strongly typed API where for example you would not be able to call should_rollback if the prediction mode is not Full

silent patrol
wet basin
#

I'm a bit confused, the tutorial seems to mention ServerPlugins and ClientPlugins as necessary plugins, but none of the examples seem to be using them? Especially the simple box example the tutorial is recreating. What's up with that? Is one or the other out of date?

pine cape
#

They are added as part of examples/common. You can also look at the simple_setuo example for a self-contained example

wet basin
#

Oh right, ofc! That makes sense, cheers ๐Ÿซก

peak ice
#

@pine cape I think I found another bug, unless I got this process wrong. Client pressed escape -> Client triggers client disconnect -> Client disconnects -> Client joins server again -> The following crash:

thread 'Compute Task Pool (2)' panicked at /Users/elizabethsuehr/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/4324f45/lightyear_udp/src/lib.rs:163:46:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `lightyear_udp::UdpPlugin::receive`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
#

I re-add the UdpIO on the client side between the first and second client connects

unkempt sedge
#

@pine cape What a refactor mr peri, truly lovely

pine cape
peak ice
#

Yeah Iโ€™ll try and fixing it, knowing itโ€™s got something to do with the link reset helps a lot!

unkempt sedge
#

2025-07-18T18:45:55.609616Z ERROR lightyear_interpolation: Could not find the receiver associated with the interpolated entity 910v13#55834575758
@pine cape Any idea on what this error means, occurs everytime I despawn a interpolated bullet entity. In a room

feral mirage
peak ice
pine cape
pine cape
#

actually maybe it's a bug? it means that the entity does not have Replicated when you despawn it

#

I'm pretty close to a new release with all the rollback fixes

#

The only blocking thing is I want the avian-3d example to be smooth like the avian-2d example is

#

I'm not too sure yet what's causing the jitters in avian-3d

#

The main difference is that entities are displayed using Transform instead of Position/Rotation

pine cape
#

I cannot release a new version because I get these errors when trying to publish lightyear_deterministic_replication

error[E0432]: unresolved import `bevy_ecs::system::ParallelCommands`
  --> /Users/charles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lightyear_transport-0.22.3/src/plugin.rs:18:14
   |
18 |     system::{ParallelCommands, Query, Res},
   |              ^^^^^^^^^^^^^^^^ no `ParallelCommands` in `system`

error[E0107]: struct takes 3 generic arguments but 2 generic arguments were supplied
   --> /Users/charles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lightyear_transport-0.22.3/src/packet/header.rs:105:29
    |
105 |     sent_packets_not_acked: IndexMap<PacketId, Duration>,
    |                             ^^^^^^^^ --------  -------- supplied 2 generic arguments
    |                             |
    |                             expected 3 generic arguments
    |
note: struct defined here, with 3 generic parameters: `K`, `V`, `S`
   --> /Users/charles/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/indexmap-2.10.0/src/map.rs:93:12
    |
93  | pub struct IndexMap<K, V, S> {
    |            ^^^^^^^^ -  -  -
help: add missing generic argument
    |
105 |     sent_packets_not_acked: IndexMap<PacketId, Duration, S>,
    |                                                        +++
Some errors have detailed explanations: E0107, E0432, E0599.
For more information about an error, try `rustc --explain E0107`.
error: could not compile `lightyear_transport` (lib) due to 4 previous errors

I truly don't get why since I successfully published lightyear_transport

#

I only get those errors with this crate

#

hm it might be due to misconfigured features

feral mirage
#

ParallelCommands is std only

#

i tried saying that on the bevy subcrates PR but didn't pass the information properly

#

but there are a few comments on justfile

molten kraken
#

Quick question, I am making a character controller who has a rigid body for physics and a child sensor collider as a hit box, but i am running into an issue where the child's position is not propagated from its parent. I am only seeing this propagation error when there are hierarchy of colliders. did a bare minimum test on the head of avian3d with one of the examples and the children collider followed as expected. then did the same test on the avian 3d example in the light year repo and it did not propagate. I re enabled the sync plugin and it still did not propagate. Have you ran into this?

pure drum
#

With this change, the RelationshipSync plugins are not needed anymore.

๐ŸŽ‰

molten kraken
#

What is the intended way to map entities inside of messages?

stray sinew
#

Does anyone know why my shared system is running twice?

        if !action_state.pressed(&SpacecraftAction::Shoot) {
            continue;
        };

        match weapon_manager.current_weapon_mut() {
            None => {}
            Some(weapon) => {
                match weapon {
                    Weapon::Railgun {
                        last_fire_tick,
                        cooldown,
                        max_speed,
                        name,
                    } => {
                        let wrapped_diff = *last_fire_tick - current_tick;
                        if wrapped_diff.abs() <= *cooldown as i16 {
                            continue;
                        }
                        warn!("last fire tick: {}", last_fire_tick.0);
                        *last_fire_tick = current_tick;
                        warn!("new last fire tick: {}", last_fire_tick.0);
                        
                        let shooting_index = weapon_manager.shooting_index;
                        weapon_manager.increment_shooting_index();

                        let salt = salt_from_client_index(
                            character_marker.0.to_bits(),
                            shooting_index,
                        );

                        warn!("entity: {} tick: {} with salt: {}", entity, current_tick.0, salt);

Its added like this in the client:

app.add_systems(
  FixedUpdate,
  shared_character_firing.run_if(not(is_in_rollback))
);

and in the server:

app.add_systems(
  FixedUpdate,
  shared_character_firing,
);

It produces these logs on the client when fired once:

last fire tick: 0
new last fire tick: 3065
entity: 282v1 tick: 3065 with salt: 3921178
last fire tick: 0
new last fire tick: 3067
entity: 282v1 tick: 3067 with salt: 3921178

It seems like even though WeaponManager is edited it gets reverted for some reason? I just started having this issue after updating to newest release.

stray sinew
stray sinew
pine cape
pine cape
# molten kraken Quick question, I am making a character controller who has a rigid body for phys...

By default, lightyear_avian takes over the sync logic with some opiniated systems here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_avian/src/plugin.rs#L103

You can disable that and use your own sync logic if you want

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

pine cape
pine cape
unkempt sedge
#

@pine cape I have a very weird bug occuring with my weapon logic would appreciate some help

#

In summary: My bullets only move y axis wise

pine cape
#

My advice would be to selectively enable trace/debug on some parts

unkempt sedge
#

Or well it works clunkily

#

After it works okay

feral mirage
#

[same project] i added a Trigger<OnAdd, LinearVelocity> and Trigger<OnInsert, LinearVelocity> and it spawns fine on the client and server, but when replicating the prespawned entity back to the client, the linear velocity gets locked on Y

#

client

2025-07-21T16:20:09.708028Z DEBUG psycho_weapons: bullet_origin=Vec3(250.50717, 1.4501282, 1.8548367) bullet_linvel=Vec3(2.5358534, -2.7493591, 9.274183)
2025-07-21T16:20:09.708053Z DEBUG psycho_weapons: Fired bullet on tick 987 on client
2025-07-21T16:20:09.708116Z DEBUG psycho_weapons: event=OnAdd linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.708194Z DEBUG psycho_weapons: event=OnInsert linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.819193Z DEBUG psycho_weapons: event=OnAdd linvel=LinearVelocity(Vec3(0.0, -2.7493591, 0.0)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.819216Z DEBUG psycho_weapons: event=OnInsert linvel=LinearVelocity(Vec3(0.0, -2.7493591, 0.0)) confirmed=false predicted=false interpolated=false pre_spawned=true

server

2025-07-21T16:20:09.746675Z DEBUG psycho_weapons: Fired bullet on tick 987 on server
2025-07-21T16:20:09.746753Z DEBUG psycho_weapons: event=OnAdd linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true
2025-07-21T16:20:09.746827Z DEBUG psycho_weapons: event=OnInsert linvel=LinearVelocity(Vec3(2.5358534, -2.7493591, 9.274183)) confirmed=false predicted=false interpolated=false pre_spawned=true

2025-07-21T16:20:09.708053Z Prespawn on the client
2025-07-21T16:20:09.746675Z Spawn on the server
2025-07-21T16:20:09.819193Z Replication back to the client

pine cape
#

You think it's lightyear related? The velocity doesn't seem to be 0 in your logs

unkempt sedge
pine cape
#

Can you add trace logs for lightyear_prediction::prespawn to confirm that this happens right after the matching?
I don't have any ideas for why a subset of the struct would be set to 0

unkempt sedge
#

sometimes

#

Mr Peri we make mistakes

#

hahahaha, lets just all forget about this

#
/// This makes our walking movement snappier, by making lin_vel zero. If no walking action is giving, it simulated high floor friction
///
/// Increases responsiveness
pub(crate) fn _snappy_walking(
    mut query: Query<(Entity, &mut LinearVelocity)>,
    walking: Query<&Walking>,
    dash: Query<Has<Dash>>,
) {
    for (controlled, mut lin_vel) in query.iter_mut() {
        // // If not walking and no dash, apply no lin_vel
        if walking.get(controlled).is_err() {
            let has_dash = dash.get(controlled).unwrap_or_default();
            if !has_dash {
                lin_vel.0 = Vec3::new(0.0, lin_vel.y, 0.0);
            }
        }
    }
}

This was the cause

unkempt sedge
#

@pine cape hot diggity damm mr peri, i have finally got to a point i can see my player mvoing again i must say

#

looks crisp

steady garnet
#

Hey, I am trying to set up my own version of the simple_box example and I keep getting this error (both when I run the client or the server):

Encountered an error in system `lightyear_inputs::server::receive_input_message<lightyear_inputs_native::input_message::NativeStateSequence<shooter::protocol::Inputs>>`: Parameter `ServerMultiMessageSender::metadata` failed validation: Resource does not exist

Any idea what I might be doing wrong?

#

Oh wait I think I am forgetting to actually spawn the Client and Server I guess lol...

unkempt sedge
#

yes

pine cape
#

Yeah the previous approach probably was buggy. Now it's smooth even with constant rollbacks

stray sinew
# pine cape I identified a bunch of issues that I outlined here: https://github.com/cBournho...

I'm trying to test the cb/fix-spaceship-demo branch but I'm getting this error:

    Updating git repository `https://github.com/cBournhonesque/lightyear.git`
error: no matching package named `lightyear_avian2d` found
location searched: Git repository https://github.com/cBournhonesque/lightyear.git?rev=9c41ad2847193ac0fb265e079d0c2cc3f3c2dc86
required by package `lightyear v0.22.4 (https://github.com/cBournhonesque/lightyear.git?rev=9c41ad2847193ac0fb265e079d0c2cc3f3c2dc86#9c41ad28)`
    ... which satisfies git dependency `lightyear` of package `deep_field v0.0.1-beta (/Users/rherv/deep_field_beta/workspace/deep_field)`

This is what my Cargo.toml looks like:

lightyear = { git = "https://github.com/cBournhonesque/lightyear.git", rev="9c41ad2847193ac0fb265e079d0c2cc3f3c2dc86", default-features = false, features = [ "interpolation", "prediction", "replication", "leafwing", "frame_interpolation", "netcode", "udp", "input_native" ] }

This might be due to the newly commented path = "../lightyear_avian/src/lib.rs" in lightyear_avian2d/Cargo.toml though I'm not sure.

pine cape
#

It is, you would need to uncomment it.
I commented it because for some reason it was blocking me from releasing

#

I still have to think of the correct solution. I think keeping the run_if(is_in_rollback) might be the simplest to avoid spawning extra bullets.

pine cape
#

@stray sinew I updated the example, it should work now.
The main change is to:

  • run the spawn_bullet system even during rollbacks (the bullets are spawned predictably from a certain input; so the PreSpawned bullets are despawned at the start of rollbacks and re-spawned during rollbacks when we replay the Fire input)
  • do not run the spawn_bullet system for remote clients. You could, but then since you receive the 'Release' input later than it's actually pressed, you would spawn PreSpawned bullets that don't actually exist. They get despawned quickly but it is distracting
feral mirage
#

@pine cape BEI Trigger<Completed<A>> being triggered every frame on server, did i miss a configuration?

pine cape
#

Hm it might be a bug? Probably because I keep predicting that the Action stays the same for future ticks

feral mirage
#

@pine cape trying to make a small repro for that but somehow i'm missing something to allow replication, i never started a project with lightyear from scratch, just joined one that was already on going, so i must be missing something minimal, either that or the setup that i'm doing is just too weird

#

basicaly i'm trying to create a server and a client using std::thread::scope

#

i can start the server and connect a client to it, but i must be missing something to replicate things from one to another

feral mirage
#

NetworkVisibility was preventing the replication

pine cape
#

thanks

#

I ran your tests, I only get one 'Completed' log on the server

#

using the main branch

feral mirage
#
#[derive(InputContext, Reflect)]
#[input_context(schedule = FixedPreUpdate)] // Completed gets spammed without this
struct TestInputs;
pine cape
#

The schedule has to be a Fixed schedule. Are you saying it doesn't work with FixedUpdate?

feral mirage
#
#[derive(InputContext, Reflect)]
// #[input_context(schedule = FixedPreUpdate)] // Completed gets spammed without this
struct TestInputs;

this was my code before creating the repro

pine cape
#

Inputs need to be scheduled in FixedUpdate to work properly, since only FixedUpdate is synced between client and server

feral mirage
#

i've already hit that many times and still continue to do so

pine cape
#

Sure, can you make a PR?

feral mirage
#

i've already put the input systems in Update many times and still continue to put them on Update even though i know it breaks things

feral mirage
stray sinew
pine cape
#

Awesome! The bullets are spawned at a distance because of the latency, but you can add some input delay to correct that

stray sinew
#

i added the spawning at a distance myself, without the distance it works fine and spawns exactly on the player

final rain
#

I was wondering, are there mechanisms to implement LOD to minimize data transfer for objects far away ?

#

Or conditionally

pine cape
#

Yes you can set a replication frequency on each ReplicationGroup (a ReplicationGroup is a list of entities replicated together, by default every entity is in its own ReplicationGroup)

#

If you are bandwidth limited, you can also set a priority per ReplicationGroup, and updates will be sent in descending order of priority

peak ice
#

Hey Periwink, I found a possible bug for steam connections, I am getting the error:

thread 'main' panicked at C:\Users\Elizabeth\.cargo\git\checkouts\lightyear-16a1ca81dacb5ed5\f96a940\lightyear_connection\src\client.rs:70:14:
A Connected entity must always have a RemoteId component
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `lightyear_Menu_Example::networking::client::client_connect`!

I fix it by added the RemoteId(Steam(steam_id.raw())) component before triggering connect and that connects with a few errors on the server (but nothing crashes).

Is manually adding a RemoteId component how to should work or is this a bug?

pine cape
#

At the top of my head I would say it's expected, but it is also possible that the RemoteId should be added as part of lightyear_steam and there is some ordering issue. Could you please open an issue so that I can take a look next week?

peak ice
#

yep:)

final rain
peak ice
#

couldn't be me lol, Im worse than Phineas and Ferb... "Where's Peri??"

unkempt ledge
#

LOL

pine cape
final rain
#

I was thinking of optimizing transactions between server clients while obfuscating or diminishing data they should not see or barely see.

#

Example. If I create a destroyer versus submarine game I won't send the submarine data until they destroyer has detected it on the sonar. (not what I'm doing but it's analogous)

molten socket
#

When can an entity with Client be safely despawned after triggering disconnection? It seems that even despawning based on Added<Disconnected> does not properly disconnect the client, so the server disconnects the client due to a timeout:

2025-07-24T01:07:22.182102Z ERROR lightyear_udp::server: Error receiving UDP packet: Une connexion existante a dรป รชtre fermรฉe par lโ€™hรดte distant. (os error 10054)
[...]
2025-07-24T01:07:22.630537Z ERROR lightyear_udp::server: Error receiving UDP packet: Une connexion existante a dรป รชtre fermรฉe par lโ€™hรดte distant. (os error 10054)
2025-07-24T01:07:22.726815Z  INFO lightyear_netcode::server_plugin: Disconnection from netcode client 4297587319730296768. Despawning entity.
pine cape
pine cape
peak ice
#

Oh I just got steam lobbies working, with the ability to request people to join your game, I think I'm ready to actually work on the porting over the game part now lol

peak ice
#

currently looking for how you setup visual_interpolation, because of this error

Encountered an error in system `lightyear_frame_interpolation::visual_interpolation<bevy_transform::components::transform::Transform>`: Parameter `Res<InterpolationRegistry>` failed validation: Resource does not exist
peak ice
#

commenting out app.add_plugins(FrameInterpolationPlugin::<Transform>::default()); seems to let everything else work (Copying your spaceship demo for now)

stray sinew
peak ice
#

what do you mean by this?

stray sinew
#

I remember getting that same error if I didn't initialize the visual correction for the component or forgot to run this system:

fn add_visual_interpolation_components<T: Component>(
    trigger: Trigger<OnAdd, T>,
    query: Query<Entity, With<Predicted>>,
    mut commands: Commands,
) {
    if !query.contains(trigger.target()) {
        return;
    }
    commands
        .entity(trigger.target())
        .insert((FrameInterpolate::<T>::default(),));
}
peak ice
#
fn add_visual_interpolation_components(
    // We use Position because it's added by avian later, and when it's added
    // we know that Predicted is already present on the entity
    trigger: Trigger<OnAdd, Position>,
    q: Query<Entity, (Without<Wall>, With<Predicted>)>,
    mut commands: Commands,
) {
    if !q.contains(trigger.target()) {
        return;
    }
    debug!("Adding visual interp component to {:?}", trigger.target());
    commands
        .entity(trigger.target())
        .insert(FrameInterpolate::<Transform> {
            // We must trigger change detection on visual interpolation
            // to make sure that child entities (sprites, meshes, text)
            // are also interpolated
            trigger_change_detection: true,
            ..default()
        });
}
#

its copied exactly from the spaceship example, what do you mean by visual correction initialization?

stray sinew
peak ice
#

ok:)

#

adding app.register_component::<Transform>(); does not change the crash, is this what you meant?

#

maybe it need linear interpolation added?

stray sinew
#

try add_correction_fn and interpolation

peak ice
#

yeah I can't add correction_fn unless i impliment a LerpFn<Transform>

stray sinew
#

After doing some digging you shouldn't have to its registered in avian's plugin here: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_avian/src/plugin.rs

        // do not replicate Transform but make sure to register an interpolation function
        // for it so that we can do visual interpolation
        // (another option would be to replicate transform and not use Position/Rotation at all)
        app.world_mut()
            .resource_mut::<InterpolationRegistry>()
            .set_interpolation::<Transform>(TransformLinearInterpolation::lerp);
        app.world_mut()
            .resource_mut::<InterpolationRegistry>()
            .set_interpolation_mode::<Transform>(InterpolationMode::None);

For whatever reason this is not running.

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

peak ice
#

ok thats good! it's probably something on my end, i'll keep looking around

pine cape
#

Maybe you didn't enable the avian feature? Or you can copy-paste the code above about registering an interpolation function for Transform

feral mirage
#

@pine cape my PR immediatly went deprecated

#

bei 0.15 completely removes InputContext

pine cape
#

Oh lol

#

Well I'll have to upgrade too

feral mirage
#

@pine cape do you know the condition for this error? lightyear_replication::send::sender: Received an update message-id ack but we don't know the corresponding group id

#

i am doing work related to bullets so i just spam the shoot button to see the spawning of the bullets, and after some time this gets blasted on the server console

feral mirage
#

@pine cape when creating multiple entities with Prespawned on the same frame, the salt needs to be distinct, right?

pine cape
#

Maybe you have many predicted entities so the message is too big to fit in one packet?

#

hm i think this will always get triggered actually even if the packet doesn't get dropped, but if the the packet is split into fragments

peak ice
pine cape
peak ice
#

it was already enabled:(

#

So i had to import lightyear_frame_interpolation separately, is that normal?

pine cape
#

I would just try to do things like in the example

#

Maybe the frame_interpolate feature needs to be enabled

feral mirage
#

@pine cape what do you suggest for pseudo-random generator that is synced between server and client?

#

when prespawning bullet they have a bit of a spread, so i would need to have a pseudorng that is the same on client and server to generate the spread

pine cape
#

I think using bevy_rand you can set a seed per entity, just make sure the seed is the same on client and server. Maybe tick + transform? Or the Prespawn hash

stray sinew
# feral mirage <@263123021336805376> what do you suggest for pseudo-random generator that is sy...

This is what I do. shooting_index is just the total amount of bullets the player has fired. It's added later because my PeerIDs are sequential starting from 1 which would cause collisions.

let shooting_index = weapon_manager.shooting_index;
weapon_manager.increment_shooting_index();

let id = character_marker.0.to_bits();
let mut rng = rand::prelude::SmallRng::seed_from_u64(id);
let seed rng.random_range(1_000_000..9_999_999) + shooting_index as u64;
feral mirage
#

I made an immutable component that is replicated that contains the seed, and on insert of that component, I add a component with the actual rng from that seed

stray sinew
#

@pine cape Will PredictionMode::Full ever be supported for immutable components? I want to have ChildOf be copied to the predicted and was wondering whats the best way to do this

pine cape
#

do you mean that you need the ChildOf component to be rolled back?

#

I think supporting PredictionMode::Full on immutable components is do-able, it just needs some work

feral mirage
#

Are you changing the immutable component that much for it to need resync every tick? (That is how I understand Full)

peak ice
#

I'm probably just going to leave frame interpolation off for now and just ignore why thats not working.

#

I currently have a more important bug to worry about, and that is steam, does anyone a working steam example they've tested with two steam accounts?

autumn furnace
#

is it currently possible to sync Transform->Position with lightyear's avian support

#

specifically, I have a bug with some unrelated code which I believe has stopped working because lightyear's replacement of the stock avian SyncPlugin doesn't sync changes to transform back into position

drifting prawn
#

Excited to see the new deterministic input stuff!

Also @pine cape I just upgraded to 0.22.5 and am seeing INFO logs being spammed, opened a ticket, I dont think they should spam by default

https://github.com/cBournhonesque/lightyear/issues/1127

GitHub

After updating from 0.21 -> 0.22.5 and adding Diffable to make the upgrade work, my logs are spammed with the following every tick. 2025-07-26T19:06:56.275729Z INFO lightyear_replication::receiv...

peak ice
#

I've identified a race condition with a closure related to steam: https://github.com/cBournhonesque/lightyear/blob/f9ad6ff3bf722860f469b4bdbc1d0a35d44baaa5/lightyear_steam/src/client.rs#L70-L82

2025-07-26T22:21:30.163607Z DEBUG lightyear_steam::client: SteamClientPlugin: LinkStart added
2025-07-26T22:21:30.163746Z DEBUG lightyear_steam::client: SteamClientPlugin: query has steamclientio
2025-07-26T22:21:30.163877Z DEBUG lightyear_steam::client: SteamClientPlugin: Linked Added, triggering connected

thread 'main' panicked at C:\Users\Elizabeth\.cargo\git\checkouts\lightyear-314564b20840a4cf\3681590\lightyear_connection\src\client.rs:70:14:
A Connected entity must always have a RemoteId component
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `lightyear_Menu_Example::networking::client::client_connect`!
Encountered a panic in system `Pipe(bevy_state::state::transitions::last_transition<lightyear_Menu_Example::MultiplayerState>, bevy_state::state::transitions::run_enter<lightyear_Menu_Example::MultiplayerState>)`!
2025-07-26T22:21:30.164757Z DEBUG lightyear_steam::client: SteamClientPlugin: closure started
2025-07-26T22:21:30.164884Z DEBUG lightyear_steam::client: SteamClientPlugin: remote Id Added
2025-07-26T22:21:30.165139Z TRACE lightyear_steam::client: Starting LinkStart for SteamClientIo on entity 136v3#12884902024. Spawning aeronet entity: 137v3#12884902025
GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

earnest fog
#

host-client doesn't work with the deterministic feature because it tries to add the ChecksumMessage twice in the common crate. Once for the client and once for the server. The registery utils panics when adding the same message. I see 2 possible fixes:

  1. don't panic in utils and ignore the duplicate add
  2. Update add_message_custom_serde in message registry to check if the type is already registered and ignore the add if it already exists
    Thoughts?
pine cape
pine cape
pine cape
pine cape
hybrid fractal
#

hi @pine cape - I'm using Lightyear 0.21, and getting a problem with Leafwing ActionStates sync'ing between client and server.

The key "pressed" ActionStates syncs just fine, but the values I get for the "current_duration" of the ActionStates on the server side are very small numbers - basically 0.0. I can't see any calls to ActionState::current_duration() in the Lightyear examples or tests, and see some comments/TODOs in the code that suggest it may not be implemented.

What do you recommend? I think I should be switching my input handling from Leafwing to BEI anyway - will key press durations sync with that, i.e. using lightyear_inputs_bei? (The game is very simple, and switching to BEI would be straightforward.)

pine cape
# peak ice I've identified a race condition with a closure related to steam: https://github...

I'm not sure I follow your logs; do you have a branch with the logs added?
My understanding is:

  • you trigger Connect

  • Connect triggers LinkStart

  • LinkStarts queues a command that adds RemoteId, spawns the aeronet entity and adds SessionEndpoint/Connecting on the aeronet entity

  • then you get an error because on Connected added, lightyear_connection expects a RemoteId. That means that Connected is added before the LinkStart command completes. However I don't get why/where Connected is added, normally it should be added by aeronet_steam

  • there is another observer in lightyear_steam that adds Connected when Linked is added; so we have to find where Linked is added

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

pine cape
# hybrid fractal hi <@263123021336805376> - I'm using Lightyear 0.21, and getting a problem with ...

Hey, I would recommend moving to BEI! Key press durations will work fine with BEI
I think the issue comes from: https://github.com/cBournhonesque/lightyear/blob/98acfd778ea2d0664df0d6086d5c8a97763b0eaf/lightyear_inputs_leafwing/src/input_message.rs#L24-L24 and https://github.com/cBournhonesque/lightyear/blob/98acfd778ea2d0664df0d6086d5c8a97763b0eaf/lightyear_inputs_leafwing/src/input_message.rs#L79-L79
Normally i would have to provide the tick duration there for the durations inside ActionState to be correct, but currently I don't pass that as context

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

hybrid fractal
earnest fog
drifting prawn
#

@pine cape My brother mentioned to you an issue we had with childing avian Colliders and them not moving with the parent.

I think this section of the code is maybe a bug? https://github.com/cBournhonesque/lightyear/blob/98acfd778ea2d0664df0d6086d5c8a97763b0eaf/lightyear_avian/src/sync.rs#L91-L110

The reason I say so is it breaks the default Bevy child transform propagation behavior; Also seems to be a regression from pre 0.21

What is the logic behind needing to update the childs Transform? In general a child will have a fixed transform and inherit global from the parent (plus its own local to parent transform). This block was doing the unwinding of the transform stack for children. I enabled the manual sync and ran this myself just deleting the child section here and it all works as expected. I am curious as to the reasons as why children need updated transforms.

I am now using just (and it all works as expected); Along with enabling transform_to_position from Avians SyncConfig

pub fn position_to_transform(mut query: Query<PosToTransformComponents, PosToTransformFilter>) {
    for (mut transform, pos, rot, parent) in &mut query {
        if parent.is_none() {
            transform.translation = pos.f32();
            transform.rotation = rot.f32();
        }
    }
}

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

pine cape
drifting prawn
#

I'm not sure the correct answer here, but it was definitly breaking sensor collider children which is not great

pine cape
drifting prawn
#

Not his ticket, but yes looks like the same issue.

pine cape
#

TBH after reading your message I still don't really understand what the issue is

#

I think the reason why the sync needs to udpate the child's Transform is that the Position component is the global position, but the Transform position is a local position. So to compute the correct local Transform we need to take into account the parent's transform as well

pine cape
peak ice
stray sinew
# pine cape do you mean that you need the ChildOf component to be rolled back?

My game requires an entity's parent be changed regularly. Currently when the server changes an entity's parent, it does not get copied to the predicted after the first time. This is my current crappy work around:

pub fn child_of_to_predicted(
    q_confirmed_character: Query<
        (&Confirmed, &ChildOf),
        (Changed<ChildOf>, With<Confirmed>, Without<Predicted>),
    >,

    prediction_manager: Single<&PredictionManager, Without<ClientOf>>,

    mut commands: Commands,
) {
    let entity_map = unsafe { &*prediction_manager.predicted_entity_map.get() };

    for (confirmed, confirmed_child_of) in q_confirmed_character.iter() {
        let Some(predicted_entity) = confirmed.predicted else {
            continue;
        };

        let Some(predicted_parent) = entity_map.confirmed_to_predicted.get(&confirmed_child_of.0)
        else {
            continue;
        };

        commands
            .entity(predicted_entity)
            .insert(ChildOf(*predicted_parent));
    }
}
pine cape
stray sinew
#

Did not know that, thanks!

earnest fog
#

This is probably why you had the panic for duplicate message type registration
```called Result::unwrap() on an Err value: DuplicateRegistration(ComponentId(265), ComponentId(339))
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/std/src/panicking.rs:697:5
1: core::panicking::panic_fmt
at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/core/src/panicking.rs:75:14
2: core::result::unwrap_failed
at /rustc/6b00bc3880198600130e1cf62b8f8a93494488cc/library/core/src/result.rs:1732:5
3: core::result::Result<T,E>::unwrap
at /home/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1137:23
4: bevy_ecs::world::World::register_required_components
at /home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.16.1/src/world/mod.rs:356:9
5: bevy_app::app::App::register_required_components
at /home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_app-0.16.1/src/app.rs:838:26
6: lightyear_messages::client::<impl lightyear_messages::registry::MessageRegistration<M>>::add_client_direction
at /home/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/8f22a63/lightyear_messages/src/client.rs:14:17
7: lightyear_messages::registry::MessageRegistration<M>::add_direction
at /home/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/8f22a63/lightyear_messages/src/registry.rs:329:9
8: <lightyear_deterministic_replication::checksum::ChecksumReceivePlugin as bevy_app::plugin::Plugin>::build
at /home/.cargo/git/checkouts/lightyear-16a1ca81dacb5ed5/8f22a63/lightyear_deterministic_replication/src/checksum.rs:216:14

peak ice
#

We finally got part of my game working again<3, I know its been doom and gloom bug reporting from me for the last few weeks, but this is exciting!

pine cape
peak ice
#

I havenโ€™t touched other clients yet lol, but thatโ€™s the plan!

autumn furnace
#
2025-07-28T17:52:44.538985Z  INFO corn_game::systems::network: starting client
2025-07-28T17:52:44.539925Z  INFO lightyear_netcode::client: client connecting to server 127.0.0.1:42217 [1/1]
2025-07-28T17:52:44.539960Z  WARN lightyear_webtransport::client: Connecting with no certificate validation
2025-07-28T17:52:44.909626Z  INFO lightyear_netcode::client_plugin: Client Netcode(0) connected
2025-07-28T17:52:44.910112Z ERROR lightyear_serde::entity_map: Failed to map entity 209v1#4294967505
2025-07-28T17:52:44.910229Z  INFO corn_game::ecs::test_cube: spawning test cube

thread 'main' panicked at /home/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/avian3d-0.3.1/src/collision/collider/backend.rs:133:31:
Entity PLACEHOLDER does not exist (enable `track_location` feature for more details)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `lightyear_replication::receive::ReplicationReceivePlugin::apply_world`!
#

well this is a riddle

#

anyone got something like this

grave coral
#

I'm also running into some strange behavior with the only smoke signal in the logs being the same Failed to map entity error.

This is while running a host-client in one window and a client that connects to it in another. Another oddity is that I don't have two copies of the relevant replicated components on the client (I would expect one Confirmed copy and one Interpolated or Predicted copy). Instead there is just one copy without Confirmed but with ShouldBePredicted or ShouldBeInterpolated

What would prevent replication from reaching a Confirmed state for these entities? I'm still looking through lightyear for some clues but I feel a bit lost for ideas.

edit: I'm on lightyear 0.22.5

feral mirage
#

which example shows replication of relationships?

autumn furnace
#

(assuming I'm correct that that is what's happening)

#

I think probably, replication messages should be discarded if mapping fails.

autumn furnace
#

I don't get it with a simple test cube scene though

#

and the entity ID in my log is one which isn't replicated :/

#

probably I'm replicating hierarchy (idk how that works in lightyear now) and have a replicated item as a child

#

yeah

#

that's what it was

#

:/ I don't know how that should work, but I know it shouldn't panic

grave coral
# grave coral I'm also running into some strange behavior with the only smoke signal in the lo...

In my case, the "failed to map entity" turned out to be a red herring that was not related to why Confirm wasn't being added.

I thought that replication and interpolation should be working because host-client by itself was working.
The missing piece was to add these two to the client entity:

PredictionManager::default(),
InterpolationManager::default(),

Host-client seems to work fine without these present, but remote clients do not.

The only hint I found to do so was its presence in the common client.rs in examples, not in the client.rs of any of the examples that contained PredictionTarget and InterpolationTarget in their server.rs. These nuggets of info being stashed away in the shared example code (examples/common) has tripped me up several times on this upgrade.

autumn furnace
#

does anyone know how hierarchy replication is managed now

#

I more or less just want to turn it off. and not replicate ChildOf at all

feral mirage
autumn furnace
#

I think what I need is to disable replicating the ChildOf component

#

since the entity's parent is not replicated.

#

yeah, that doesn't seem to do it, it is still trying to replicate ChildOf

pine cape
pine cape
# grave coral I'm also running into some strange behavior with the only smoke signal in the lo...

HostServer mode is a bit brittle in the sense that it tries to emulate the normal replication behaviour by adding fake components (Predicted, Interpolated, etc.) that the client usually queries on: https://github.com/cBournhonesque/lightyear/blob/main/lightyear_replication/src/host.rs#L62-L62

Confirmed is added here: https://github.com/cBournhonesque/lightyear/blob/5dc3dc3e17a8b821c35162b904b73eea0e1c69be/lightyear_prediction/src/spawn.rs#L17

  • is there only one entity with ReplicationReceiver LocalTimeline and PredictionManager? You might have multiple
  • try adding debug logs for lightyear_prediction::spawn
GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

GitHub

A networking library to make multiplayer games for the Bevy game engine - cBournhonesque/lightyear

#

Oh i see you solved the issue. Yeah the HostClient doesn't need these because it doesn't actually do Prediction, since the host-client is on the same timeline as the server.

#

I'd love a PR that helps simplify the examples! I would still need a way to run the examples in all the different modes (host-server, etc.) since that's helpful to test various functionalities. But i'd be fine with a 'default' version where most of the code is inside the example folder itself, since it has been a repeated feedback that the shared.rs folder is hard to grok

pine cape
# autumn furnace yeah, that doesn't seem to do it, it is still trying to replicate ChildOf

What are you trying to do exactly?
You have

  • P1 (non-replicated parent)
    • C1 (replicated child)
      and you want to not replicate ChildOf, since the entity mapping failure (entity mapping fails because the parent is not replicated) causes a panic on the remote?

You want to keep ChildOf in your protocol, because some other entities might want to replicate it?
You can add the ComponentReplicationOverrides::<ChildOf>::disable_all() component that will disable sending ChildOf on all ReplicationSenders.
https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentReplicationOverrides.html

autumn furnace
#

That would work, but id prefer to not have ChildOf even registered for replication

grave coral
# pine cape I'd love a PR that helps simplify the examples! I would still need a way to run ...

Yeah, part of this is just the vegetables I have to eat when using a cutting edge major refactor release of a library where the dust around documentation hasn't settled yet. Once more of that dust settles and I have more confidence in what I'm doing in lightyear, I wouldn't mind pitching in on improving examples/documentation. Right now I just think I'd break more things than I'd fix.

pine cape
# autumn furnace That would work, but id prefer to not have ChildOf even registered for replicati...

I register it by default; but you can disable it with

app.register_component::<ChildOf>()
  .with_replication_config(ComponentReplicationConfig { disable: True, ..default() })

https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentRegistration.html#method.with_replication_config
https://docs.rs/lightyear/latest/lightyear/prelude/struct.ComponentReplicationConfig.html#structfield.disable

grave coral
#

I will say though, the refactor is dope, and I like the new approach overall now that I've had enough time to get a decent grasp on how it works. Networked events (RemoteTriggers) are sick, and the shift to using more components for everything has had a lot of cool positive side effects in our project. We're also hyped about the BEI stuff as well.

pine cape
#

Thanks! I'm really happy with the move to heavily rely on components/observers, + the fact that the code base is relatively client/server agnostic.

Some things are still tricky, like the lack of observer ordering.
I also had to roll out my many-to-many relationships for Replicate (since an entity can be replicated via multiple senders); but hopefully bevy will introduce a better version than mine.

earnest fog
autumn furnace
#

I wonder what the right way of going about building a demo web-build is.

#

I got everything building for wasm and running in browser single player. And networking is working native in host-client mode

#

I could build a dedicated server (might want a inprocess headless server in the end regardless.)

#

but I think a thin network proxy would be better

#

then just, whoever opens the website first can be host-client and I don't need server build

#

but

  1. I think I'd have to modify or extend lightyear to support having a server in wasm
  2. I'd have to figure out how to setup the proxy.
earnest fog
#

Registering ChecksumMessage twice in host-client mode is now problematic because the message type registry (kind_map) seems to think it's 2 different types
Encountered an error in observer lightyear::protocol::ProtocolCheckPlugin::receive_verify_protocol: the message protocol doesn't match

earnest fog
#

I found the issue. It's because the hasher for the message registry was being modified by adding it a second time. Even with this fix, there's still some errors for host-client mode seemingly on the host-client server. Lots of checksums mismatch but I think it's because the host-client server isn't running the simulation for some reason https://github.com/cBournhonesque/lightyear/pull/1136

pine cape