Skip to content

Multiplayer review fixes: race messages carry their race number (0.4.1) - #6

Merged
lowlevelmetal merged 10 commits into
mainfrom
integration
Oct 11, 2026
Merged

lowlevelmetal merged 10 commits into
mainfrom
integration

Conversation

@lowlevelmetal

Copy link
Copy Markdown
Owner

Fixes from the multiplayer code review after 0.4.0. Version 0.4.1; the network protocol goes to 19, so 0.4.0 and 0.4.1 can't play together.

  • Race messages leaking into the next race:
    • Session::resetReplication left the players' inputs, own-car states, prop states and rules states queued from one race into the next.
    • ENet also holds unreliable packets under load, so a race's input could arrive after the next race had started.
    • Inputs and car states number their samples from 1 each race and carried no race number. A host that took a leftover input numbered 5000 dropped the client's real inputs for about 83 s.
    • Fix: the queues are cleared between races, and Session stamps every race-time message (inputs, car states, traffic, props) with its race number and drops mismatches. The rules' messages already carried theirs.
    • The new test confirms both directions: a leftover is dropped, and the next race's own messages arrive.
  • Joiner password field: it takes 25 characters like the host's (MM2: AddTextField 0x19 for both).
  • READY: shared cruise traffic settings now clear a joiner's READY.
  • Players who quit: a player who quit the race no longer gets every prop state in full.
  • Debug autopilot: it no longer rebuilds and logs every frame for an unroutable destination.
  • Inputs: each input message is encoded once.

Testing: 1051/1051 tests pass with game data, without it, and under -D_GLIBCXX_ASSERTIONS.

🤖 Generated with Claude Code

lowlevelmetal and others added 10 commits October 10, 2026 15:58
Session::resetReplication cleared the snapshot, ambient and traffic queues
but not the players' inputs, the own-car states, the prop states or the
rules states, so whatever a race left untaken was handed to the next one.
And ENet holds unreliable packets under load, so an input could arrive
after the next race had started: car states and inputs number their
samples from 1 each race and carried no race number, so a host that took
an input numbered 5000 dropped the client's real inputs (from 1) as
already applied for about 83 s. The queues are now cleared between races,
and Session stamps every race-time message (inputs, car states, traffic,
props) with its race number and drops one from another race (protocol 19;
the rules' messages already carried theirs).

Found by the multiplayer code review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The joiner's password field takes 25 characters, as the host's does
  (both are UIMenu::AddTextField with 0x19 in MM2): a host's 25-character
  password could not be typed, and five tries locked the joiner out.
- A change to the shared cruise traffic (on/off, traffic and police
  density) clears a joiner's READY like every other host setting.
- A player who quit the race is no longer sent the host's prop states:
  with no car it counted as near every slot and got them all, in full.
- The debug autopilot keeps a destination it cannot route to instead of
  rebuilding its driver and logging every frame.
- A client's input message is encoded once: Session::sendPlayerInput
  returns the bytes it sent, as the other send calls do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The race number test now also sends race 2's input and car states and
checks they arrive, so a mismatch between the stamping and the check would
show as lost states rather than pass unnoticed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The bundled DirectMusic library used C11 <threads.h> wherever the compiler
had it, and MSVC's C11 threads live in vcruntime140_threads.dll (VS 2022
17.8+). The packages ship the other VC++ runtime DLLs but not that one, so
openmm2.exe failed to load before main on a PC without a recent VC++
redistributable (found by unpacking the 0.4.0 installer and running it
under Wine: status c0000135). MSVC now uses the library's Win32 threads,
as MinGW already did.

The Windows CI job could not see this, since its runner has Visual Studio:
a new step reads every packaged program's and DLL's imports with dumpbin
and fails when a VC++ runtime DLL is not in the package, or any other DLL
is neither there nor part of Windows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When copying the game data failed, setup removed all of
<install dir>\gamedata. On a reinstall or update over an installation
that had copied the data already, a copy that then failed (a bad disc, a
full drive) deleted the good copy too.

Setup now notes whether gamedata\ was there before it copies. If it was
not, everything in it came from this copy and is removed as before. If it
was, nothing is removed (--import-source deletes its own .part files on
failure and replaces an archive only once it is complete), and when the
earlier copy still passes --check-source, openmm2-install.ini keeps
pointing at it rather than at the source that just failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Setup now finds the installed version (HKLM\Software\OpenMM2: InstallDir,
Version) and updates it in its folder:

- The welcome page says what will happen: a new installation, an update
  from X to Y, a repair (the same version again) or a downgrade. An update
  skips the licence and directory pages, and the game data page, pre-filled
  from openmm2-install.ini as before, reads Install: Next, Next, Install.
  /D= still installs elsewhere.
- An older setup asks before it replaces a newer version. A silent one
  refuses with exit code 3 unless given /ALLOWDOWNGRADE.
- Before it changes anything, setup checks that openmm2.exe and
  mm2tool.exe are not running, by opening them for writing (a running
  program's executable cannot be), and asks to close OpenMM2 with
  Retry/Cancel. A silent install stops with exit code 4. The uninstaller
  checks too.
- Setup writes install-manifest.txt, the list of the files it installed
  (generated with the File lists). Before copying the new files, an update
  removes the previous version's: those in its manifest, or, for 0.4.1 and
  earlier, which wrote none, every name those releases installed
  (LegacyFiles.nsh, from the release installers 0.1.0 to 0.4.0, which
  include the MSVC runtime DLLs) and this version installs. The
  uninstaller removes what the manifest lists, so files a newer version
  dropped no longer stay behind, and neither does the folder. Manifest
  entries that could reach outside the install folder, into gamedata\ or
  openmm2-install.ini are skipped. gamedata\, openmm2-install.ini and the
  per-user settings are never removed.
- The choice of shortcuts and developer tools is recorded (StartMenuShortcut,
  DesktopShortcut, Tools) and preselected on the next update. For 0.4.1 and
  earlier, setup keeps what is there: a deleted shortcut is not created
  again. A shortcut that is switched off is removed.
- /D= naming another folder moves the installation: the old folder loses
  its program files, uninstaller and firewall rule, keeps gamedata\ and
  openmm2-install.ini (whose game data the new installation keeps using),
  and is removed if nothing else is left. The registration stays single.
- A file that cannot be written (another program holds it) stops setup
  (AllowSkipFiles off) instead of being skipped, which left an old file in
  place and still reported success. Such a failure ends with exit code 1,
  not NSIS's 2, which here means the /SOURCE game data was rejected.
- A silent update that re-checks the last installation's game data (say, a
  disc that is not in the drive) keeps the setting instead of failing
  with exit code 2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RunWithProgress read the copy's output with System::Call
'*$7(&m4096 .r3)'. For such an output the System plug-in allocates
NSIS_MAX_STRLEN (1024) bytes and then copies all 4096 into them, so each
chunk of output overran the heap by 3 KB. Under Wine this made setup crash
on exit (access violation, exit code 0xC0000005) depending only on where
things landed in memory: the same installer crashed every time from one
path and never from another, and the 0.4.0 scripts do the same. Windows'
heap is as likely to be hit.

Read at most NSIS_MAX_STRLEN - 1 bytes per chunk and convert them with
MultiByteToWideChar into a buffer of the right size, as UTF-8, which is
what OpenMM2 prints.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
packaging/windows/test-installer.sh stages a Windows build's app and tools
components, packs them into installers of made-up versions (one with extra
files the next one drops) and builds the v0.4.0 installer scripts as the
last release without update support. With synthetic game data (three small
PKZIP .AR archives that pass --check-source) it checks, in fresh Wine
prefixes, the installed files, install-manifest.txt, openmm2-install.ini,
the registry and the shortcuts for:

- a new installation, and uninstalling it;
- an update from 0.4.0 (MSVC runtime DLLs removed, game data and settings
  kept, a deleted desktop shortcut not re-created, the tools kept), then
  through a version that drops files, a repair, a silent downgrade with
  and without /ALLOWDOWNGRADE, openmm2.exe held open and running (setup
  and uninstaller), another file held open, a copy of the game data that
  fails over an earlier copy, and a manifest with entries that point
  outside the folder or into the game data;
- updates without game data, then uninstalling: nothing is left;
- /D= moving the installation to another folder;
- the wizard, driven by a small helper that reads and clicks it: the pages
  of a new installation and of an update (Next, Next, Install), the
  repair and downgrade texts, the downgrade question and the Retry/Cancel
  question while OpenMM2 runs.

Wine uses its null graphics driver, so nothing appears on a desktop. The
MinGW CI job runs it with the Windows makensis.exe 3.13 through Wine (NSIS
is not packaged for Arch Linux), pinned by hash and cached.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lowlevelmetal
lowlevelmetal merged commit ff73b6a into main Oct 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant