Two things were still missing once a session ran for a while: a peer that had
drifted apart from the authority had no way back short of restarting, and a
connection that blinked cost the player their seat and handed their vehicle to
the AI while the other driver was still sitting in it.
- a client that disagrees with the authoritative digest for 120 steps in a row
asks to be put back in line, and the server answers with a fresh snapshot.
There is a cooldown between requests, so a correction that needs a moment to
take hold does not turn into a stream of them
- the server hands out a session token with its hello and remembers it. Present
it again on the next connection and you come back as the same peer, with the
same id
- a lost connection no longer removes the peer from its crews: the seat is held
for a minute. Come back in time and nothing changed; stay away and the seat
is given up properly, handing the vehicle back to the AI only when that
leaves the crew empty and the AI was driving it in the first place
- after applying a snapshot a peer that finds itself on a crew walks into that
cab, which is what makes a reconnect land the player back where they were
- the vehicle roster is sent to a joining peer right before its snapshot, so
the crew section resolves to names instead of waiting for the next broadcast
Joining meant replaying backbuffer.bin from the very first frame of the
session, so the longer a session ran, the longer it took to get in - and the
whole history had to be kept for it.
- network/snapshot: a chunked container with a version of its own. Every
section carries its length, so a peer built before a section existed reads
a newer snapshot and simply walks past what it does not know. Version 1
carries the session (tick, clock, weather, the state of the random engine),
the vehicles (position on the track, speed, direction, controls, brake
pressures, whether the AI is driving) and the crew roster. Switches,
memcells, signals and the event queue are the next sections
- joining is now: handshake -> load the scenario -> CLIENT_READY -> the server
takes a snapshot on the spot and sends it -> the peer goes live. Because the
snapshot is taken at the moment the peer becomes active there is no backlog
to replay at all; the frame by frame catch-up is gone
- the server refuses a peer that loaded a different scenario, with a readable
reason instead of a silent desync
- reconnecting into a session that is already loaded asks for a fresh snapshot
straight away rather than waiting for a loader that will not run again
- a vehicle is only put back on the rails when it really is somewhere else; the
arithmetic that turns the recorded bogie offset back into the nose distance
place_on_track() expects mirrors what that function itself does
- the session log is still written out - it is useful when picking a desync
apart - but nothing depends on replaying it any more
The protocol hung off whatever frame the renderer happened to be drawing, and
the only thing standing in for synchronisation was the sum of every vehicle's
coordinates with a random number added on top - a value that cannot be equal on
two machines and therefore reported a desync on every single step.
- Global.simulation_tick: the authority counts the steps, a client takes the
number from the authoritative frame it is executing. FRAME_INFO and the
command requests carry it, so the network timeline has one unambiguous order
independent of frame rates
- network/statehash: a versioned FNV digest of the gameplay critical state -
vehicle position, speed, direction, brake pipe and cylinder pressure, plus the
clock. Values are quantized so that float noise is not mistaken for drift, and
vehicles are folded in under their own identity and summed, so the digest does
not depend on the order the scenario created them in. Switches, memcells and
the event queue are the next candidates; they go in when stage 7 pins down
what a snapshot contains
- a client compares its own digest against the authoritative one and reports the
drift when it starts and then only now and then, rather than every step; the
overlay says how long it has been out of step and at which tick
- refusing to compare at all when the two builds disagree on the digest layout
EU07_NETWORK_VERSION is now 4.
A vehicle is not owned by one player: two people share a cab, so the session
needs a crew relation rather than an owner field. The server keeps it and is
the only one allowed to change it.
- network/session: vehicle_crew { entity_id, crew, ai_before_claim } and a
crew_registry holding the roster. the host goes through exactly the same
path as a remote peer (claim -> crew -> grant), only without transport
- CLAIM_VEHICLE / CLAIM_GRANTED / CLAIM_DENIED / LEAVE_VEHICLE / CREW_UPDATE;
the server validates the vehicle exists, is present in the scenario and has
room in the crew, and answers a refusal with a reason the lobby shows
- the cab of a claimed vehicle is created by the authority posting the
ordinary replicated entervehicle, so every peer builds it and the vehicle is
never moved. the claiming peer only walks in once the cab appears locally
- AI transitions follow the crew size: 0 -> 1 remembers whether an AI was
really driving and takes the vehicle over, 1 -> 2 and 2 -> 1 change nothing,
1 -> 0 hands the vehicle back but only if there was an AI driver to begin
with. driver mode no longer fires aidriverdisable of its own in multiplayer
- losing a connection removes that peer from every crew it was on; a vehicle
with somebody still on board carries on untouched
- the lobby lists who is on board, offers "join crew" for a vehicle another
player already occupies, and lets a player step off
The AI and cab handling runs as a reconciliation pass rather than as edge
triggered actions, so a request that has not taken effect yet (the cab needs a
frame to appear) is simply repeated, with a cooldown to keep it off the wire.