
Four tight back-to-back builds covering one focused arc: the multiplayer flow from launch to lobby is now coherent end-to-end. Sign-in is on your terms. Ranked is server-enforced clean. Quick Match actually queues. Mixed-AI lobbies are fully editable.
Until v0.31.30, every Steam launch unconditionally fired the auto-Steam-link flow. That's the right default for the common case — but the wrong default for shared machines (one PC, multiple Steam profiles, single shared HexArmy callsign), smurf or test accounts, or pre-Steam HexArmy accounts already linked to a different Steam profile.
The fix is to make it user-selectable. Two radios in the profile panel — Automatically use Steam account (default — byte-identical to v0.31.29) and Use custom login — plus a nested Automatically sign-in checkbox that controls whether the session cookie persists across launches. The setting is stored per-machine (localStorage), so you can run auto at home and custom on a shared laptop without one overwriting the other.
Custom mode still respects Steam linking. When you pick custom login, the manual sign-in form's "Steam detected" banner still triggers and routes the submit through /api/auth/steam-link — typing your real callsign + password also links the Steam profile to that account in the same atomic step.
Quick Match — the only flow that normally creates ranked games — has always sent all-human compositions, so no live ranked game was ever affected. But anyone who reverse-engineered the request body could have farmed ELO against effortless AI wins. The leaderboard is zero-tolerance for that kind of pollution; one exploited account taints the whole season. The guard runs at the endpoint, before the game row is created. There's no path through.
The column lives on player_stats, not users — it migrated over in an earlier schema reshuffle. Other endpoints that need it (`sockets/multiplayer.js`, `routes/stats.js`) were already pointed at the right table; only the Quick Match queue join was wrong. The catch-all returned generic 'Failed to join queue' with no SQLSTATE or detail, so the failure was opaque.
Fix: LEFT JOIN player_stats with COALESCE(elo_lifetime, 1000) so brand-new accounts default to ELO 1000 — same default the column itself uses. Bonus: the 500 catch-all now surfaces err.message, err.code, and err.detail in the response body. Next failure tells you exactly what's wrong, in one pass.
Two distinct fixes shipped together because they're both polish on the v0.40.0 mixed-AI lobby surface.
AI slots are visible and editable. In v0.40.0 the player-slot renderer only had two paths — filled-with-callsign or empty — so AI slots fell into the empty branch and showed "Waiting for player..." forever. Now there's a third render path keyed on slot type:
The socket contract gains an optional forSlot field on mp:select-faction and mp:select-team; if present and the requester is the host AND the target slot is AI, the write goes to that slot's column instead of the requester's. Non-host requests, or writes targeting human slots, are silently rejected.
Single-player has had Low/Medium/High Terrain Density since v0.31.4; MP didn't, and v0.31.26's themed-map fix (which made density actually do something on Continents, Archipelago, Pangea, Highlands, Forest Basin, River Networks) made the missing MP toggle visible — host had no way to set it.
New column multiplayer_games.terrain_density defaults 'medium'. Host's pick is sent on /api/multiplayer/create, mirrored back on /api/multiplayer/join so non-host clients pick it up, and applied to gameState.terrainDensity before initMap runs — so themed generators respect it from turn 1.
If you never opened the profile panel, Steam still launches your account exactly like v0.31.29. If you only play ranked through Quick Match, the v0.40.1 guard is invisible. If you create MP games without AI, the lobby looks identical. Every change is additive or surgically scoped — nothing existing breaks.
Update via Steam → Properties → Betas to grab v0.40.3 today. Promotion to the default branch follows after a few days of testing on beta.
Full release notes: hexarmy.com/release-notes.html
Pick your sign-in. Queue actually works. Lobby fully wired. Ladder stays clean.