Repository navigation
Host-authoritative multiplayer and cross-platform determinism (0.4.0) - #5
Merged
Merged
Conversation
With OPENMM2_NET_TRACE set, the host writes each shared car near a player at the session time its state belongs to (the AI's last step for a car on its rail, the physics step's for a police car or a knocked one), and every knock and refused hit report; a client writes the received cars where its car meets them each frame, with the session time its own car reaches in that frame's steps, and the hits it reports. Comparing the files measures how far each client's traffic is from the host's at the same moment. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…vergence OPENMM2_NET_TRACE gains three opt-in lines: D for every player's car as the frame draws it (the machine's own car marked), K for a collision between two players' cars in this machine's simulation, and C for a correction of a predicted car. netprobe syncreport reads one trace per machine and prints how far apart the machines show each car at the same wall-clock moment, the frames in which a car moves further than its velocity explains, which collisions between players each machine had and how far apart every machine drew the two cars at that moment, and the corrections. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
For the host-authoritative network cars: CarSim and Trailer save and restore everything a sample changes; World::replaySample runs one sample of a car (and its trailer) alone, against the city, the instances around it and the other movers held still, so a client can run its own car's samples again after the host corrected an earlier one without moving anything else; World gets hooks around every sample for the players' per-sample inputs; and a car can draw its wheels' bump numbers from its own stream, so the host and the car's own machine simulate it alike whatever else each one simulates (MM2 shares one rand() across the game). Single player sets none of these. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… rails A client drew the host's traffic and police a trip and a playout delay in the past while its own car was in the present, and its car met them there: measured through a 60 ms (+-20) relay, a moving traffic car was 2.6 m (median, 3.7 m at the 90th percentile) from where the host had it at the same session time, 4.0 m over 150 ms. The host also stamped its messages with the frame's time while the cars were at the AI's last 30 Hz step, putting every car another 17 ms behind and making them surge. Now the host stamps a message with the AI step's time (the police and the knocked cars moved to it along their velocity) and sends, for a car on its rail, its acceleration and the heading's turn per metre measured from the AI's last two steps (protocol 5; 1 bit when both are 0). The client shows every car at the time its own car reaches in the frame's steps: a rail car predicted along an arc at its speed and acceleration (stopping at 0), a police car or a knocked car along its velocity turning with its yaw rate. Its collisions take the newest prediction; the drawing blends a newer message's correction away over 100 ms (above 4 m it jumps). The same relay: 0.02 m median, 0.06 m at the 90th percentile; 150 ms: 0.03 m and 0.14 m. MM2's network cruise has no traffic; MM2 predicts its network cars forward from the last packet (mmNetObject::PositionUpdate). OPENMM2_DEBUG_TRAFFIC_LEAD_MS shows the cars that much later, to measure the prediction over the longer horizon a host-simulated car will need. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Protocol 5 for the host-authoritative cars: PlayerInput (client to host, unreliable) carries every sample's controls the host has not acknowledged, numbered and repeated until acknowledged (a frame like the one before it is one bit), with the resets the client's rules asked for; CarStates (host to each client, unreliable) carries the last input the host applied to that client's car, how many of its inputs were waiting, the car's state after it at full precision, and every other player's car as a snapshot. Both are read within ranges (no wrapping sequence, positions in the city, a rotation matrix, bounded values), only during a race, and a joiner's inputs within a budget of 90 messages a second. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MM2 sends nothing about props: mmGameMulti and mmNetObject carry the cars, and every machine knocks its own props with its own simulation of each car. OpenMM2 0.3 did the same, except that a network car is a kinematic body there, which can never break a prop (dgImpact::CalcImpact could not stop it), so every prop a player knocked stood on every other screen, and the host's traffic knocked props on the host only. The host now owns them (the maintainer's decision: the host is the authority for everything): - PropState (protocol 5): every occupied slot of the host's ring of knocked-over props (dgBangerManager), about 20 times a second while any moves, every 0.5 s otherwise; complete, so a slot left out is empty. A placed prop is named by its index (every machine places the same props in the same order; a checksum travels with each message), a piece by its prop and BREAKnn part, a thrown car part by its car and part. - PropKnocks event: the placed props the host broke loose, reliable, and all of them so far to a machine that has just loaded the race. - A client shows the host's ring in mirror slots, interpolated a playout delay in the past, and breaks a prop when it shows the host's knock. Only its own car may touch its props (phys::Instance::acceptsContact): what its car knocks moves at once and hands over to the host's piece when both rest; a knock the host never makes is undone after 2 s. - A moving kinematic body breaks a banger as a body of its mass would and gives it all of the relative velocity, so the host's copies of the other players' cars knock props until they become host-simulated cars. OPENMM2_NETPROPS=local keeps 0.3's behaviour for comparison, and OPENMM2_DEBUG_NETPROPS=<file> traces each machine's props for `netprobe propdiff`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It reads a host's and a client's OPENMM2_NET_TRACE files and prints how far the client's traffic and police were from the host's at the session time of the client's car, the cars near the client one machine had and the other did not, the police targets, the hit reports and knocks, and the cars the client knocked loose itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sync A piece the host is simulating was a ghost on a client: its car drove through a flying cone that hit the host's copy of it. A client's mirror of a host piece is now an object its car can touch at any time; the active that takes it over starts from the host's motion (dgBangerActive::Attach starts a placed or resting prop at rest), and the piece stays where the client left it until the host's has moved and come to rest too. Creeping props (a meter sliding down a hill that phSleep never stops) go out every 200 ms instead of 50. OPENMM2_DEBUG_FOCUS=prop:<index> frames a placed prop, or its first piece once knocked, on every machine alike. Tests: the messages and their limits (test_prop_state.cpp, the fuzz corpus and decoders), and a host and a client in one process through a lossy link (test_prop_sync.cpp): the host's knocks and pieces on the client, a predicted knock handed over and one undone, other cars passing through a client's props, moving kinematic bodies breaking props, loss and reordering, the catch-up, thrown car parts, refused input, and on retail data the same placement on every machine. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he ground they cover A client's car met the received traffic as kinematic instances of infinite mass: it bounced off a car as off a moving wall, while on the host the same contact gives the rail car a body and the two cars push each other (aiVehicleInstance::AttachEntity, aiVehicleActive); the car itself flew off only a round trip later, when the host's knock came back. TrafficBodies now takes its traffic through an interface (the AI's for the host and single player, unchanged), and a shared-traffic client gives it the received cars on their rails (game::NetTrafficCars). The client's own car hitting one attaches a body there as on the host, so the collision runs the host's physics; only that car knocks cars loose (the police and the other players' cars reach the client in the host's messages). The car stays the client's until a host message shows it off its rail too, or still on it half a round trip and 150 ms after the hit (the host did not knock it); the drawing then blends from the local body to the host's car. The cars off their rails on the host stay kinematic instances. The host's trace showed rail cars moving over the ground at 70-80 % of their AI speed in turns (ai::Traffic moves a car along its curves by their parameter) and not at all when held at the end of a lane at speed. The host now sends a rail car's speed over the ground when it differs (12 more bits), and the client predicts its position with it while its velocity (what its body takes when hit) stays the AI's. TrafficClient::poseAt gives a car's physics pose at any time, for a client that replays its own car's steps. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Once a wreck standing still on the host was shared with the 6.8 m/s its body's velocity still said for 40 s, and the client, predicting it to the present, showed it 1.2 m ahead all that time. A body that has moved less than 5 cm in 100 ms is now shared without velocity or spin, whatever its velocity says. mm2tool nettrace also gives the error at the time the client showed each car (for OPENMM2_DEBUG_TRAFFIC_LEAD_MS). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A moving kinematic body broke every banger it hit, so with OPENMM2_NETPROPS=local each machine's kinematic copies of the others' cars knocked props too, which OpenMM2 0.3 never did. The body now has to be allowed to (Body::kinematicBreaksBangers): the host sets it on its copies of the other players' cars, which knock the props for everyone; everywhere else a banger holds against a kinematic body as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The error of each car the client's car hit, over the second after the hit: how well the client's view of a collision matches the host's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At traffic density 1 the cars within 200 m of a client did not all fit in a message: it held at most 96, and the farthest ones were cut. 12 % of the host's cars within 200 m of the client were missing there, and cars at the cut came and went (45 times in 100 s, at 170-195 m) as the order shifted. A message now holds up to 160 cars, a known rail car beyond 160 m gets its state every fourth message (the client predicts it in between), and when the cars still do not all fit, the ones the client has count as 25 m nearer, so the car at the edge of what fits stays until a new one is clearly nearer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With more cars near a client than fit, the cars at the edge came and went nearly 2000 times in 100 s at traffic density 1: whether a far car's turn for its state fell in a message changed its size from 13 bits to 130, and with it which cars at the edge still fit; a car left out was gone for the client, and coming back new it needed its state, pushing out others. The host now chooses the cars by their usual cost (a far known car's state every second or fourth message, its id in between), then writes them; a far car whose turn it is goes without its state when it would not fit with it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The host's copy of another player's car broke a banger and gave it all of the relative velocity, as an infinitely heavy body: a parked car hit by a client's car flew off at the car's full speed on the host (and so on every screen), about twice what the client's own car gave it. It now meets the banger exactly as dgImpact::CalcImpact meets a car of its mass (the car's phInertialCS): the same break test and the same share of the motion; only the car's own response is left out, which its owner's machine simulates. Within 5% of a simulated car's piece in the test. The props trace lists every placed prop once (P lines), for finding props and parked cars to drive into. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A car ploughs through 60 props on the host: every knock reaches the client, the largest message is 425 bytes and the crash costs about 5 KB/s for a few seconds; the ring of 40 knocked-over props (dgBangerManager) bounds what any message can carry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
game/net/PlayerCars: NetCarDriver applies one sample's input to a car the same way on every machine (the gearbox keys, the gold's mass and throttle cap, the finish brake, the grid hold, mmGame::UpdateSteeringBrakes, Cops and Robbers' regeneration) and carries out the resets a client's rules ask for; HostInputQueue takes a client's numbered inputs, starts once a few are in hand, applies one per sample, repeats a missing one for a quarter of a second and then lets the car coast, and drops what comes too late or too far ahead; CarPrediction keeps a client's car's inputs and states, sends what the host has not acknowledged, and when the host's state for a sample differs puts the car there and runs the later samples again on their own; CorrectionBlend draws what a correction moved away over a few frames. The input frame gains Cops and Robbers' regeneration flag. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The maintainer's decision: the host is the authority for every player's car. In a network race every player's car takes its input once a sample. A client predicts its own car from its inputs at once and sends them (all the host has not acknowledged, each frame); the host simulates each client's car from them as a full car of the level (mmNetObject's vehCar, polygonal bound, type-3 mover), so collisions between players, with the traffic and with the props happen there once, and sends each client 20 times a second the last input it applied, the car's state after it and every other player's car. A client puts its car back to the host's state when they differ and runs its later inputs again; it draws the other cars interpolated from the host's states as before, and runs its samples up to 3% faster or 2% slower to keep the host a sample or three of its inputs ahead. Resets (the water, a fall, a debug respawn, Cops and Robbers' repairs) travel as commands with the inputs and happen on the host at the same sample. The host decides every car's damage: it records its simulated cars' dents, parts and impacts and sends them about each player's car (VehicleDamage subjects 513 on), and refuses a player's word on its own car; a client shows its own car's damage from them and keeps only its predicted sparks and sounds. OPENMM2_NET_TRACE's divergence lines now use the machine's shared monotonic clock (each process's own clock made the traces of instances started apart incomparable), syncreport also reports how close each machine drew two colliding cars around the collision, and OPENMM2_DEBUG_NETCARS logs the corrections and what the cars cost. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A function-local struct with default member initialisers made with std::vector<T>(n), and polymorphic objects (worlds, prop sets) built inside lambdas in the tests: both have broken MSVC before. The ring's per-slot state is a namespace-scope struct, the tests' helpers are functions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/multiplayer.md gains a Props section (what MM2 does, the protocol, the client's rules, the measurements, the deviations) and the props in the channel, message, untrusted input, event, per-frame contract, automation and testing notes; docs/bangers.md what BangerSet carries for network games; docs/parity/openmm2-only.md the extras and deviations. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/multiplayer.md: the message's stamp and rail motion, the prediction to the present, the client's collisions, the trace lines and mm2tool nettrace, protocol 5; docs/parity/openmm2-only.md: the new OpenMM2 extras; the start of the review record (docs/review/multiplayer-desync-traffic.md). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Protocol 7 for the props (5 is the players' cars, 6 the shared traffic's). The host's players' cars are simulated bodies now, so they knock its props as single player's cars do: the kinematic copies' banger breaking (Body::kinematicBreaksBangers, calcBangerImpact's kinematic branch) is gone again, and dgImpact::CalcImpact is MM2's for every body. The parts the host throws off a client's car carry that player's tag, like its own car's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
At traffic density 1 the cars at the edge of what fits still came and went 190 times in 100 s: a near car turning or slipping costs more bits in one message than in the last, the farthest known car no longer fit, and back as a new car it needed its state. A car new to the client now waits while the known ones would leave less than a tenth of the budget, which absorbs those changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the host simulating every player's car, a client's car runs ahead of the host's simulation of it by the time its inputs take to reach the host and wait there. The client measures that lead from the host's states of its car (the host's session time after sample n against the time it ran sample n itself) and shows the received traffic at its car's time plus the lead, so its car meets the cars where the host's simulation of it will. Its own knocks are confirmed on the host's clock (the host simulates the same hit at that time), so the half round trip leaves the window. The hits now happen on the host: the client's hit reports (TrafficHit, the host's applyNetTrafficHits and TrafficBodies::knock) go. The host chooses each client's cars round its simulated car, which also fixes the traffic no longer being sent at all (the clients send no snapshots any more, and the host looked for them). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the host corrects a client's car, the samples run again now meet the other players' cars where each sample first met them, not where they stand in the frame of the correction: pushing another car from behind, replaying against its newest place made the corrections swing back and forth by up to 4 m. Also an experiment for the record, OPENMM2_NET_OTHERS: "ahead" places and draws the other cars where the host will have them when it runs this machine's sample (extrapolated from its newest states), "ghost" keeps this machine's car from touching them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The props' lines (placed props, knocks and their causes, undone predictions, and every 250 ms of session time the props down, the pieces and parts shown, the cars' damage and the own car's damaging impacts) go into the session's divergence trace with lowercase tags, and `netprobe syncreport` compares every pair of machines' props after the cars. OPENMM2_DEBUG_NETPROPS and `netprobe propdiff` are gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A client replays its car's samples when the host corrects it (World::replaySample), and everything it meets there holds still. A standing prop held like a wall (calcBangerImpact against a body without an ICS stops the car after the break limit) and a knocked-over piece like an infinitely heavy body: a replay through props took up to 6 m/s off the car that the real sample had not (1.5 m corrections after a hit on a street light). The held instance now meets the car with the mass a real hit gives it (Instance::heldInertia: the active dgBangerActive:: Attach would give the prop; a copy of a moving piece's phInertialCS), still taking nothing itself, and a light one (under a quarter of the car's mass) gives way after the first hit of a replay, as the real one flies off (World::beginReplay, which CarPrediction calls). A car replayed into a meter loses what a real one does (within 1 m/s), not all of its speed. A client driving alone through props had no correction in 40 s: the host's simulation of its car through the same props gives the same samples. The parts the host's record takes off a client's own car are no longer thrown again on the client (the host's ring shows them), and the props trace takes each car's dents from the host's records. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the host simulating the client's car from the same inputs, the car the client knocked loose moved 3 cm (median) from the host's while the client simulated it, but handing it to the host's messages as soon as they showed it knocked jumped it 3.7 m: the host's knocked car, a trip old, predicted along its velocity while it tumbles. The local body now leads until it comes to rest, the host's car is 5 m from it, or the host has it back on its rail; meanwhile the received car is not also a moving instance. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Integration at b33bebc: the simulation's maths through libm::, every target with -ffp-contract=off -fno-fast-math. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/review/multiplayer-desync-traffic.md: protocol 12 against 782f469 in the same scenarios plus a second police chase: what was fixed (O1-O3 and four more found on the way), what stays open, the bandwidth and the edits outside this area. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
docs/review/multiplayer-desync-rules.md, "Second round: the rough edges": MM2's water and fall handlers (mmGame::Update, mmGameMulti::HitWaterHandler, mmMultiCR's), what changed in protocol 14, the runs (the bay's water in a cruise and in Cops and Robbers, contested gold) and their results before and after: checkpoints confirmed with the loss tail gone (99th percentile 767-776 to 417-441 ms at 150 ms and 5 % loss), the water agreeing at the same samples and now the host's, no predicted pickup undone (3 of 7 contested runs before), the bandwidth; R9 and O1-O6. docs/multiplayer.md: a client's predicted water as its own bullet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a client stalled, or its inputs were lost for longer than the host repeats them, the host coasted its car on and then dropped every input as late, until the client's 3% faster pace made the gap up: a minute for two seconds (one 75 s run here had the host's car of the client parked while the client drove off 500 m). A state acknowledging a sample the client has not run now makes it count the samples up to it, the round trip and the host's margin as run (CarPrediction::skipTo), so its next inputs reach the host in time; the state then corrects the car to where the host coasted it. The players' cars' section of docs/multiplayer.md describes this round's states, near cars and drawing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A car's full state now also holds, when the sample's push has moved the body past it, the bound's matrix as the collisions saw it (the next sweep starts there), and the collider that pushed it hardest in the sample (CopyLastMatrix reads it) as a player's number. Rebuilt without them, a car pressed against another drifted from the host's: a push against a braking car was corrected by 0.93 m; now twice by a centimetre (tests/game/test_player_cars.cpp). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NetGameStart.SlowLoaderIsWaitedForAndBothGoTogether wanted each machine's countdown and go within 25 ms of the shared times, which a frame under a loaded ctest run can exceed. NetRaceStart counts down and goes in the first frame at or after those times on each machine's session clock; the test now checks exactly that (the frame before was earlier, the frame itself not). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The companions keep both the shared traffic's (police and knocked cars driven or rebased by their own functions) and the players' (each sample's input from the host's inputs ahead, the state's pusher mapped to this machine's colliders); the full traffic states go out with the CarStates after every frame that ran a sample. The players' cars this round take protocol 13 (11 on this branch); a near player's car now counts as a player's in what it hits, so it knocks the shared traffic loose on the client as the simulated police do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Integration at 8e74c96 (the shared traffic's second round, protocol 12): the rules keep protocol 14, RulesState after TrafficFull. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Before and after on the merged builds: another player's car's knocks now fall as that car meets the prop (they fell 140-400 ms after it passed; 138 of 155 confirmed by the host), a missed prediction stands again in 0.7-1.8 s instead of 2 s, a client's pieces knock no placed prop, each client's message carries its own area first; every knock still shared, the pieces within 3 mm, 0.1-6.4 KB/s to each client. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> # Conflicts: # docs/multiplayer.md # src/net/Protocol.h
…ection Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The shared police and knocked traffic cars a client now simulates with its car (TrafficFull, or knocked loose by it) may knock its props as the near players' cars do: a prop one of them hits falls as it meets it, the host's knock confirming it (TrafficBodies::carOfBody tells a traffic car's body). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> # Conflicts: # docs/multiplayer.md # src/net/Protocol.h
A car's full state carries both the water (its vehSplash and the water handler's time) and the contact's bound and pusher; the host's reset check is the rules' narrowed one. The players' cars of this round take protocol 15. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A client ran the host's braking car into a parked car 1.9 s before the real one reached it; the prediction was undone at 2 s and the host's knock came a moment later, so the car stood up and fell again. While the host still has the knocking car within 6 m of the prop, its prediction now waits up to 4 s. Protocol 16 in the docs (13 on this branch). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the build merged with the traffic's and the rules' second rounds: every knock shared, the pieces within 3 mm of the host's, all 66 predictions of the last batch confirmed (own car, other players' cars, the police and knocked traffic), another player's car's knocks falling as it meets the prop; 1045 of 1045 tests with and without assertions; protocol 16. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The players' cars of this round take protocol 17. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A client drew every near car at its player's present, a lead back on its trail: right for the host's car, whose player's present is the host's, but another client's screen runs ahead of the host's as this one's does (each predicts its car its inputs' trip and the host's margin ahead), so its car was drawn about 150 ms behind it. Another client's car is now drawn at this machine's car's time always (three players ramming, client to client: 0.02 m at the median while the clients were near each other, against 0.38-0.54); the host's car as before. The players' cars' section of docs/multiplayer.md has this round's measurements. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The third round's section of docs/review/multiplayer-desync-cars.md: what the runs showed (the near cars' old inputs, where they were drawn, a contact's bookkeeping, a client behind the host's samples, O8's cause), what changed, every scenario before and after against 0.3.1 and integration before and after the rules' and props' second rounds merged, what drives the corrections (knocked-over props in the rear runs), the traffic (O8, fixed by the traffic's second round), another player's car's knocks for the props (10 of 57 undone on integration, 3 of 43 here), the checkpoint race's start (not reproduced), a race started in the water (the rules' O5, still open) and the bandwidth. O5 dropped, O8 and O9 fixed, C9-C11 and O10-O11 added. The parity and sync notes and two comments carry protocol 17. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he fuzz test The mutated CarStates the decoder accepts now also keep the near set within three cars, a near car's inputs ahead within 16 (their flags, events and extra mass in range) and every full state's carried forces, pushes, tyre resistances and bound matrix in range. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A client's car pushing knocked-over props and pieces met them where the host's messages showed them (a playout delay in the past) or as its own simulation had them, and was corrected at nearly every state. The host now sends, with the CarStates of each client (about 20 times a second), the pieces round that client's car in full (net::PropFullMsg): a moving piece's body as its simulation has it, with the bound where the sample's collisions saw it and what pushed it hardest (the sweep against that collider starts where the push left it); a resting one's exact frame. The client puts them to those states and runs them with its car when its samples run again (CarPrediction companions), so the replays reproduce the host's samples exactly. Also: a piece in full takes this machine's own piece of the same prop before the slot is shown; a piece in full knocks placed props as the host's does (a prediction, with a replay ghost); its prop's ghost goes once the host's state of it is known; up to three datagrams a state, the nearest pieces in the host's world's order. Measured in tests/game/test_prop_push.cpp (a car pushing a pile of 18 props through the rest, 60 states a second, 100 ms each way): no correction with the pieces in full, 269 without. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pieces round a client's car in full (protocol 18) in the props' section of docs/multiplayer.md, and the round in the review: the chase's corrections are not the props' second round (the same binary corrects 0 or 112-167 times a minute from run to run, before and after it), what an exact replay of a pushed pile needed step by step, the rear-end runs before and after (corrections over 1 cm near a knocked piece 208 -> 87 a minute, the largest 2.8 m -> 0.77 m), the bandwidth, and another player's car's knocks on the merged build (31 of 50 predicted, 15 of 50 before the cars' third round). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
src/net/PlayerCars.h and src/game/net/PlayerCars.h shared a name. For a quoted include MSVC searches the directories of every file up the include stack before the -I paths, so "net/PlayerCars.h" from a file under src/game/ found game/net/PlayerCars.h and the wire types were never declared (PR #5's Windows build: C2039 'CarInputFrame' is not a member of 'mm2::net'). The wire header is now net/PlayerCarState.h, like the other *State.h messages; no other quoted include in the tree resolves to two headers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multiplayer rebuilt around the host (the maintainer's decision: "the server needs to be the authority of everything"), plus cross-platform determinism. Version 0.4.0, network protocol 18. The records are
docs/review/multiplayer-desync-*.mdanddocs/review/multiplayer-determinism.md; the design is indocs/multiplayer.md.Host authority
ResetRules.Determinism
libm(sin, cos, atan2, exp, log, pow, hypot) and no float contraction on any target, so Linux and Windows simulate the same bits.Measured
Through
netprobe relaywith internet-like latency and loss:Testing
-D_GLIBCXX_ASSERTIONS.🤖 Generated with Claude Code