ALC_MAJOR/MINOR_VERSION only report the OpenAL API spec level (always 1.1),
which is misleading. Additionally log alGetString(AL_VERSION) (e.g.
"1.1 ALSOFT 1.24.2") once a context is current, and relabel the spec line.
OpenAL does not re-route on its own, so unplugging the active output device
(e.g. headphones) mid-game left the sim silent until restart. Poll ALC_CONNECTED
(ALC_EXT_disconnect) at ~1 Hz and, when the device is lost, reopen playback on
the current default output via alcReopenDeviceSOFT, preserving the context and
all sources. Tokens are declared locally since the bundled AL headers ship no
alext.h; the entry point is resolved at runtime, so this is a no-op (with a log
warning) on OpenAL Soft builds too old to provide ALC_SOFT_reopen_device.
Enabling GLM_FORCE_DEFAULT_ALIGNED_GENTYPES makes glm::vec3 16 bytes (padded),
but the listener orientation was passed to alListenerfv as
reinterpret_cast<ALfloat const *>(glm::vec3[2]). With the padding the six
floats OpenAL reads are at.x, at.y, at.z, PAD, up.x, up.y - the 'up' vector is
taken from padding garbage, corrupting the listener basis (right = at x up) and
swapping left/right of every positional sound.
Build the 6 floats explicitly instead of reinterpreting a padded vec3 array.
std::uniform_real/int_distribution has UB when min>max. The old "fancy Random"
used interpolate(a,b,t) which tolerated a>b; the migration to
std::uniform_real_distribution did not, and only one inverted call site was
fixed by hand. Swap the bounds inside Random()/Random(int)/LocalRandom() so
every call site is safe, and reorder the two inverted literal calls in
Driver.cpp (fActionTime buzzer / departure delay) for readability.
The dumb3d->glm / utilities-simplification refactor replaced the old custom
clamp() (which tolerated Lo>Hi, returning the upper bound) with std::clamp,
where inverted bounds are undefined behaviour. Only a few sites were patched
afterwards, by hand, with std::minmax plumbing (incl. a stray `static` inside
a per-axle loop). Others silently produced wrong results in the AI braking /
acceleration path.
Add a safe_clamp() helper (normalizes inverted bounds) and use it ONLY where
Lo<=Hi cannot be proven at compile time (config min/max pairs, sign-dependent
expressions, container underflow). Sites with constant bounds or [0, x>=0]
keep std::clamp. Remove the manual std::minmax workarounds and rewrite the
damaged-track jolt code value-first.
Consolidates platform-specific config-directory logic (APPDATA on Windows,
~/Library/Application Support on macOS, ~/.config on Linux) into a single
user_config_path(filename) function in utilities/utilities.{cpp,h}, removing
copy-pasted #ifdef blocks from application.cpp, editor/editorSettings.cpp,
input/{keyboard,gamepad,drivermouse,uart}input.cpp.
Pressing Alt toggles picking mode. This is fine for keyboard and mouse usage,
but some use cases, such as disabling picking mode when a finger is placed
on a thumbstick and enabling it back when it's lifted, require finer control.
Allow to always enable picking mode when pressing Alt with Shift, and to always
disable picking mode when pressing Alt with Ctrl.
With tonemapping, clouds need to stand out above 1.0 to reach proper
white levels.
Note that this should get disabled if tonemapping isn't used, in case
it's made properly configurable in the future.
We're supposed to output linear values there, as sRGB transfer function
gets applied either by GL framebuffer or FBOUT later on.
min_ev and max_ev also need to be adjusted, as they're tweaked for
a pipeline with exposure compensation. The current values lead to a very
washed-out image, which was "hidden" by subsequently applying gamma twice.
It may make sense to revisit these values in the future to better match
the rendering pipeline.
Shift isn't used as that would affect joystick controls and we
probably should discourage the user from roaming around in external
cam mode too much, so they won't forget that they're not in regular
free fly out of the cab.
Both setweather and settemperature expect overcast as their second
parameter. Passing zero to settemperature always overwrites whatever
gets passed to setweather.