Give multiplayer a compact join card in Quake 4's own style, over a softened map - #149
Merged
Merged
Conversation
The connect-time join screen was the whole in-game menu on its Join tab. It is now its own panel: gametype and map, whether the match is in warmup, the scoring limit, how full the server is, the server name, and only the choices that mode allows - join, pick a side, or spectate. The panel is authored inside the plain 640x480 canvas and never reads gui::virtual_screen_x_expand, so it keeps a 4:3 shape and stays centred on a wide display instead of stretching across it. It deliberately does not dim the rest of the screen; the game module softens the live view behind it instead, which is what makes a partial-screen card readable. onActivate routes to the panel when the game module publishes initial_join and otherwise restores the menu chrome and its intro animation, which is what previously re-showed the chrome a frame after the join event had hidden it. Escape from the panel closes it and leaves the player spectating; the full menu is one more Escape away. A refused team switch reports in place on the card. The interactive multiplayer launch configurations now pass ui_autoJoin 0, the shipped default, so a manual run starts at the join screen. Automated multiplayer profiles still set it to 1 explicitly, and AGENTS.md now says which is which rather than implying one rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The join card was legible but generic - a flat rounded slab that could have come from any game. Quake 4's interface has a specific vocabulary and the card now speaks it. The frame is the shipped tooltip edge set, which carries an olive rail along its inside and cuts a 45-degree chamfer off every corner; the bottom cap is the top cap mirrored, the way the stock tooltip does it. The body is the menu's own screen texture under a dark gradient. Every button is a real Quake 4 plate - 45-degree cut at the bottom left, highlight down the left and along the bottom, fading away to the right - with a marine-font label and the little corner marker that is the most recognisable piece of the whole interface, and the menu's own hover: snap to orange on enter, ease back over 300ms on exit, with the label growing by a hundredth. Settings and Leave Match sit in the header as the stock gear and arrow symbols rather than as words. No new art was authored; all of it already ships in the retail PK4s. The frame comes in two heights so a gametype offering one join choice does not get a panel sized for four. The soft focus behind the panel was invisible unless you ran the classic OpenGL path. The OpenGL composite read parm 6 as a tint amount and blurred by a fixed 0.6 regardless, while the native Vulkan pass reads it as the effect strength - so a request for no tint became a request for no blur, and asking for a gentle blur produced a heavy one. The composite now reads the controller the way Vulkan does: parm 6 is strength, parm 4 converts to the same normalized falloff, and the colour tint is driven by the approach colour's own alpha. A zero-strength request draws nothing instead of a fixed blur, and the classic path, the OpenGL renderer module and Vulkan now produce the same image. This also settles the Arena Campaign's depth of field, which had the same split. docs/dev/ui-visual-design.md records the rules so the next panel does not have to be reverse-engineered from this one: the art vocabulary, palette and type scale, button and frame anatomy, the hover contract, the 4:3 authoring rule, how to soften a scene behind a partial-screen panel, and the traps - a GUI parse error is only a warning, and window properties do not resolve "$desktop::" names the way scripts do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
… default Validation Script Smoke failed on weapon_zoom_view_alignment. CI pins openQ4-game by SHA, and that pin was fourteen commits stale: it still carried the UpdateZoomGuiViewState call from the 2D overlay pass that the test forbids, so the failure was already there on main. It also predates the join card's own model, so this branch could not have gone green against it either way. Both workflow pins move to bc60187, the current openQ4-game main, which carries the zoom fix and the join screen's UpdateJoinScreenGui / SetJoinScreenSoftFocus. vscode_fast_build then failed on the second thing this branch changed: the contract still required every MP launch configuration to set ui_autoJoin to 1. The interactive configurations now pass 0, which is the shipped default and starts at the join screen; the automated MP profiles it checks next - the listen-server helper, the RenderDoc helper, the renderer benchmark and the stock-asset baseline - still pin 1, and the contract still insists the value is set explicitly exactly once. Its two AGENTS.md assertions follow the wording that rule now uses. All 156 scripts in the smoke list pass locally against the new pin, the full build links against it, and a Team Deathmatch join on Vulkan comes up clean with no GUI parse warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both sides moved the CI GameLibs pin, which is the whole conflict. main's pin (f5596b6) is a descendant of the two openQ4-game commits this branch needs - cdcde5e for the join screen's model and bc60187 for the blur controller parameters - so taking main's value keeps both intents and carries its own newer multiplayer effects and clock work as well. Nothing else on main touched this branch's files. Verified against the merged tree: all 156 scripts in the validation smoke list pass with OPENQ4_GAMELIBS_REPO pinned to f5596b6, the full build links, and a Team Deathmatch join on Vulkan plus a Deathmatch join on OpenGL both come up clean with no GUI parse warnings. Co-Authored-By: Claude Opus 5 <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.
Connecting to a multiplayer server opened the entire in-game menu on its Join tab — ten navigation buttons, a full server dump, a chat log and a text field, all to answer one question. This replaces that with a compact join card drawn in Quake 4's own menu style, over a map that is pushed out of focus behind it.
What changed
A dedicated join card. It carries only what a player needs before picking a side: gametype and map, whether the match is in warmup, the scoring limit that gametype actually enforces, how full the server is, the server name, and just the choices that mode allows — join, pick a side, or spectate. Settings and Leave Match sit in the header as symbols. A refused team switch reports in place on the card instead of on the Join tab nobody is looking at.
The button set comes from the gametype descriptor via two GUI state flags, so the GUI no longer carries its own copy of the gametype table, and the scoring-limit lookup moved into one helper so the card and the Join tab cannot drift apart. The frame comes in two heights, so a Duel offering one choice does not get a panel sized for four.
Quake 4's visual idiom, from shipped art. No new interface images were authored; everything is already in the retail PK4s.
tooltip_edge/tooltip_midfor the frame, which carries the olive inner rail and cuts a 45° chamfer off every corner. The bottom cap is the top cap mirrored withmatscalex -1 matscaley -1, which is how the stock tooltip does it.b6_light,b4_light): 45° cut at the bottom left, highlight down the left and along the bottom, fading to nothing at the right. Marine-font label, corner marker, plates tinted olive rather than white.main_menu_mouseover/main_menu_selectionon both ends.gfx/guis/common/gear1_stillandgfx/guis/mainmenu/icon_arrowfor the header symbols.The panel is authored inside the plain 640x480 canvas and never reads
gui::virtual_screen_x_expand, so it keeps its 4:3 shape and stays centred on a widescreen display instead of stretching across it. It deliberately does not dim the rest of the screen — Quake 4 doesn't, and the softened backdrop does that job.A renderer parity fix for the soft focus. The OpenGL half of the Raven special-effect blur read parm 6 as a tint amount and blurred by a fixed 0.6 regardless, while the native Vulkan pass reads it as the effect strength; parm 4 was inverted between them too. So a request for no tint became a request for no blur on Vulkan, a gentle blur came out heavy on OpenGL, and the OpenGL renderer module produced a half-blurred quadrant with a hard seam.
rvspecial_medlabs.fsnow mirrors the Vulkan contract — parm 6 is strength, parm 4 converts to the same normalized falloff, the tint is driven by the approach colour's own alpha — and a zero-strength request correctly draws nothing. The Arena Campaign's depth of field had the same split and is fixed by the same change.ui_autoJoinwas never the gate. It has always defaulted to0; what suppressed the join screen was all 98 VS Code MP launch configurations passing+set ui_autoJoin 1, and because the CVar is archived one such launch made it stick permanently. Those now pass0. Automated profiles (renderer_gameplay_benchmark.py,stock_asset_baseline.py, the dedicated smokes) still set1in their own code, andAGENTS.mdnow says which is which instead of implying one blanket rule.docs/dev/ui-visual-design.mdrecords the rules so the next panel does not have to be reverse-engineered from this one: the art vocabulary with a material table, palette and type scale, button and frame anatomy with the exact hover script, the 4:3 authoring rule, how to soften a scene behind a partial-screen panel, a pre-flight checklist, and the traps.AGENTS.mdcites it as a project rule.Companion game-module change
This needs
openQ4-gameatbc60187or later, onmain:cdcde5e— the join screen's model (UpdateJoinScreenGui,SetJoinScreenSoftFocus) and a stickyjoinScreenPendingflag.bc60187— the blur controller parameters that go with the shader change here.Notes for a reviewer
Activate()d twice per open —idMultiplayerGame::StartMenuactivates it, thenidAsyncNetwork::ExecuteSessionCommandhands the result tosession->SetGUI, which activates it again; a listen-server host is also offered the screen again after its own player entity respawns. The old one-shotinitial_joinGUI state bit was consumed by the first of those, so every later activation fell back to the full menu. That is why the offer is now tracked in code and cleared only when the player answers.onActivaterestores the chrome and its intro animation on the non-join path.anim_insetsp_main::visible 1a frame after the join event hid it, which is the trap that made the first version of this look like it had not been applied at all.mpmain.gui, line N: expected ...and silently drops the rest of that windowDef. Related: windowDef properties do not resolve$desktop::names — only scripts do — so amatcolorproperty has to be literal numbers. Both are written up in the new design doc.Validation
.install/.mp/q4dm1,mp/q4dm2andmp/q4dm3across Deathmatch, Team Deathmatch and Tourney, onr_renderApi gl,gl-moduleandvulkan, at 1600x900. Screenshots captured for each; all three backends now produce the same softened backdrop.openq4.logon every run.match_control_ui_contract,competitive_match_layer,lang_table_encoding,competitive_match_localization,match_control_localization_bridge,arena_campaign,mp_bot_navigation,renderer_budget_contract,stock_asset_baseline,settings_menu_coverage,mp_bot_server_menu,docs_link_integrity,release_tooling_safety.#str_entries added to all six language tables.docs/dev/release-completion.mdanddocs/dev/releases/v0.12.0.md.Not covered: clicking the buttons was verified by reading the emitted commands rather than by driving the mouse, so the join/spectate/team actions themselves are unchanged stock commands but have not been clicked end to end in an automated run.
🤖 Generated with Claude Code