17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 21:29:19 +02:00
Commit Graph

7 Commits

Author SHA1 Message Date
fb94dcfa2b multiplayer: replicate the machinery you hear, not only the machinery you set
A bystander heard nothing of somebody else's converter, compressors, brake air
or relays. Two different reasons for that, and they need two different answers.

The devices whose sound runs for as long as they do - the converter, the main
compressor, the pantograph compressor, the releaser - are outputs of a physics
that a peer only half runs for a vehicle it does not own. Their running flags
travel now, alongside the permits that were already going.

The clicks and hisses are a harder case: MoverParameters::SoundFlag is raised
and wiped inside a single frame, so sampling it when an update happens to be
put together would catch almost none of them. They are collected as the vehicle
finishes with them, just before it clears the field, and the accumulation rides
along with the next update. On arrival they are added to whatever the receiving
peer's own physics produced, and its sound handling plays them once and wipes
the lot, exactly as it does for its own.
2026-09-07 08:48:30 +00:00
d0306f3d4a multiplayer: replicate what a vehicle is heard and seen doing
The horn never reached anybody else. It is not a command a bystander can carry
out: OnCommand_hornlowactivate sets MoverParameters->WarningSignal from inside a
cab, and a peer that is not sitting in that cab has no TTrain for the vehicle,
so the command was dropped on arrival. Only the result of it can travel, and it
was not being sent.

The vehicle state now carries what a vehicle is heard and seen doing rather than
only what it is being told to do: the horn flags, the emergency brake horn and
its alarm chain, the wheel revolutions and the engine revolutions. The last two
are what turn the driven wheels and set the engine note, so somebody else's
locomotive no longer sounds and looks like it is standing still while it rolls
past.

The state also goes out a little sooner, every sixth frame instead of every
tenth: a horn can be pressed and let go inside a couple of frames, and an update
that arrives after it is over is no use.
2026-09-07 08:25:08 +00:00
a0537356bb multiplayer: correct a train by sliding it, not by putting it down again
Reversing on the host turned the whole set around on the client, and the
locomotive ended up looking like something derailed making its way across the
scenery. Both come from the same place: a routine correction was calling
place_on_track(), which sets the axles from scratch and works from the
vehicle's own idea of which way it faces. That is right for a peer joining with
nothing to go on, and wrong for a train that is already running - the placement
reorients it.

A correction now slides the vehicle along the track it is already on, by the
difference between the two bogie offsets, which is the same operation the
physics performs on every step. When the two sides disagree about which piece
of track the vehicle is on, it is left alone until they agree; guessing there
is what put a locomotive beside the rails. place_on_track() is kept for the one
case it suits, a peer joining the session.

Also:

- taking a seat works with the game paused. The whole thing sat inside the
  "has any time passed" branch, so with the world stopped the lobby button
  changed the roster and nothing else happened
- the debug panel gets a Network section, shown only in a session: role, peer,
  tick, bytes and messages each way with a running rate, two charts of kB/s so
  the cost of a session can be read off, how far the worst vehicle is out of
  place, and whether the state digest agrees. The separate "network" overlay
  window is gone - everything it said lives there now, together with the lobby
  and refusal messages that had nowhere to be seen
2026-09-07 07:49:47 +00:00
c0dbf99f1a multiplayer: whoever drives a train runs it, and their ride is the smooth one
Until now the server ran the physics of everything and a client re-ran it from
the server's frame times, corrected a few times a second. That is the worst of
both: the client's own train never moved smoothly, its controls answered a
round trip late, and the two copies still sat metres apart between corrections.

Simulation authority now follows the train:

- the first person aboard a train runs its physics on their own machine, and
  what they produce is what the rest of the session is told about it. A train
  with nobody in it belongs to the host. Every peer works the owner out the
  same way, from the crew of its lowest numbered car, so nobody has to be told
- a peer never applies corrections to a train it runs itself. Its own physics
  is the authority on it, and overwriting that is precisely what made the
  owner's ride stutter
- a client publishes the state of its own trains upstream, ten times a second.
  The server takes it, filtered so a peer can only move the trains it is
  entitled to move, and passes it on to everybody else in its own stream
- so the picture is: your train is smooth for you and may stutter for the
  others; their trains may stutter for you and are smooth for them

Two things follow from it:

- commands for the train you run take effect the moment you press the key
  instead of after a trip to the server and back. They are still sent, so
  everybody else sees them, and the echo is dropped when it returns
- the client no longer replays the server's frame times out of a jitter buffer.
  It runs on its own clock, like the host does, and simply carries out the
  commands as they arrive. Playing back somebody else's frame times meant the
  world ran at the wrong speed whenever the two machines drew at different
  rates, and every buffered frame sat between the player and their controls

Also: the server only sends a frame when it has commands in it, or every tenth
one to carry the tick, instead of one per rendered frame.
2026-09-07 00:19:40 +00:00
0b75e701fc multiplayer: a player works a train, not a car - and put the corrections right
Second live test. Four things, three of them the same mistake seen from
different angles: the session was reasoning about single vehicles where it
should have been reasoning about trains.

- a peer may now operate anything coupled into the set it is sitting in. This
  is what "cabchangebackward rejected: not a member of the crew of that
  vehicle" was: the crew was recorded against one car of a four-car unit, and
  the cab switch addressed another
- every vehicle gets a network id, not only the ones worth driving. Commands
  are addressed by that id, and a player has to be able to walk into any car
  of their train. "peer 3 cannot take en71-017rb_4: no such vehicle in this
  session" was the driving trailer of an EN71 - a full desk, no engine, and
  nobody assigned to it, so the old test missed it. Having a driving position
  is what counts now, engine or not
- the roster groups vehicles into the trains they are coupled into and the
  lobby lists one row per train. Coupling is worked out afresh every time, so
  a train that comes apart becomes two, and because a crew entry sits on the
  car its player occupies, each half keeps the people who were riding in it
- walking to another car of your own train is not a new claim and asks nothing
  of the crew capacity, which counts people per train

Positional correction never actually did anything: it identified a track by
name, and most track in a scenery has none, so the lookup returned nothing and
place_on_track() was never reached. The client sat 178 m out of place for
minutes, reporting four vehicles "put back on the rails" every time while
moving none of them. Tracks now travel as their index in the path table, which
comes from the scenery file and is therefore the same on every peer.

Also from the test:

- the camera jumped in and out of the cab on the client whenever the host
  reversed, because the correction was forcing the server's CabActive and
  CabOccupied onto it. Which cab somebody sits in is theirs alone - two people
  sharing a vehicle each have their own - so it is no longer replicated
- the correction no longer touches the control positions of the train the local
  player is working. Their crew's commands reach every peer on their own, and
  snapping a lever back a fraction of a second after it was moved is what made
  the reverser look dead
- the frame buffer was sized for the old lockstep, up to sixty frames deep.
  Every frame in it is input lag, so it is down to twelve with a target of one
- asking the AI of a vehicle to step aside is given five tries and then said
  once, instead of every thirty frames forever; and a resync request now has a
  real cooldown
2026-09-06 23:39:13 +00:00
9dc78a65dc multiplayer: stream the authoritative state, and let a client walk into a cab
First live test showed three things wrong at once.

A client could not take a vehicle. Walking up to a locomotive and pressing the
enter-vehicle key posts entervehicle, which stage 4 classed as a world-wide
command and refused outright ("reserved for the session host"), so the client
never got a cab and every later control command died locally for want of one.
Walking into a cab is a request for a seat, not a world command: the authority
now turns it into a crew claim, for the host through the same path as for a
remote peer, and answers it the way the lobby button is answered.

Nothing but commands was ever replicated. The server sent dt and the commands
it ran, and the client simulated the rest for itself - so a pantograph the host
raised before the client joined, the contents of the memory cells the signalling
reads, brake pressures and switch positions simply never crossed the wire. The
server now streams the authoritative state a few times a second: whatever has
changed since the last one, and the whole of it every so often so a peer heals
from anything it missed. The snapshot carries the vehicle's inputs - line
breaker, battery, converter and compressor permits, pantograph valves, cab,
lights, doors - and lets the receiving peer derive the rest, plus the memory
cells and the switches.

Desync detection demanded that two state digests be equal, on a design where
the client deliberately runs its own physics. They never can be, so it reported
a desync permanently and asked for a resync every five seconds forever, which
fixed nothing because the next step disagreed again. The digest is a log line
now; what is measured instead is how far out of place the routine correction
found us, and a full resync is only asked for when even that is not catching up.

Also: a consist is put back on the rails as a whole or not at all. Correcting
one vehicle while its neighbour stayed put is what was stretching couplers
until they tore - the last test moved six vehicles out of eight, every time.

And the lobby window collapsed to nothing, because its size_max was left at the
-1 default while size_min was set, so imgui was handed a maximum smaller than
the minimum.
2026-09-06 22:19:28 +00:00
54d8b112b8 multiplayer stage 7: versioned snapshots instead of replaying the session
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
2026-09-06 19:37:28 +00:00