New prey - #23
Open
themuffinator wants to merge 647 commits into
Open
Conversation
Multiple fixes and improvements across Linux packaging, renderer, UI font handling, build tooling, and validation tests: - Normalize launcher/executable naming to case-sensitive openQ4-* in docs and desktop entries; packaging and tests now validate desktop Exec fields and ensure packaged Linux launchers/binaries are present and executable. package_nightly.py gained POSIX executable handling and desktop-entry validation logic. - Make Meson build directory removal safer by resolving paths, refusing to remove root/repo or non-Meson dirs, and validating build markers before rm -rf. - Renderer fixes: unbind textures after fullscreen post-process and scrub GL/material state after SSAO world-depth capture to avoid stale material/alpha-test side effects and tinting. - UI/font changes: add glyph horizontal guard to avoid atlas edge sampling artifacts, introduce PaintGlyph, propagate transform flag to DrawStretchPic calls, and update extensive font-parity self-tests/metrics. - Restore and clean up idWindow::IsSimple logic (re-enable editor/gui checks and additional conditions). - Linux sys improvements: honor XDG_DATA_HOME for default save path and increase Sys_EXEPath buffer with overflow handling. - Validation/test scripts updated to search for both openQ4- and OpenQ4- prefixed binaries and to require executability on POSIX hosts. These changes tighten packaging correctness, prevent runtime rendering/font artifacts, and harden build and validation tooling.
Make idWindow::IsSimple() always return false to preserve the retail Quake 4 GUI alias surface. Retail GUI scripts often animate per-component aliases (e.g. forecolor_w, matcolor_w) that the compact idSimpleWindow path doesn't expose; forcing full idWindow semantics fixes popup animation regressions (missing Yes/No labels and translucent button backgrounds). Also update the release-completion checklist to note the GUI popup animation fix.
Skip noisy duplicate-decl warnings for OpenQ4 override files by adding DeclManager_IsOpenQ4OverrideDeclFile and omitting warnings when source files match *_openq4.def. Refine loading-screen selection: read mapDef loadgui earlier, special-case guis/loading/intro.gui to prefer gfx/guis/loadscreens/e3_load.tga if present, and otherwise fall back to map screenshots. Make material program lookup quieter by checking for the md5r file path before attempting an ARB program lookup. Remove stale installer material overrides and prune a VSCode launch configuration entry; update the release notes checklist to mention these improvements.
Rename content/baseoq4/def/weapons/napalmgun_openq4.def to content/baseoq4/def/weapons/00_napalmgun_openq4.def to enforce lexicographic ordering. This is a pure rename — file content unchanged (100% similarity).
Introduce a settings-menu refactor: add docs and a JSON registry for the new settings structure and coverage tests. Update mainmenu.gui to centralize crosshair color state (gui::g_crosshairColorChoice) and remove legacy scroll apply namedevents. Update localization strings (English, French, Italian, Spanish). Apply platform and API updates across macOS, Linux, POSIX and Win32 (SDL3 migration-related changes), and adjust UI/core code (Console, DeviceContext, SliderWindow, UserInterface, Session_menu) to match the refactor and platform changes. Add tests for settings menu coverage and renderer supersampling safety.
Avoid pulling x86-only CPU-frequency fallback parsing into ARM64 builds by restructuring Sys_ClockTicksPerSecond: the nanosecond-clock path for non-x86 hosts now uses a guarded one-time initialization and the x86 fallback code remains behind the appropriate #else/#endif. This prevents ARM64 compile/runtime issues and keeps monotonic nanosecond timing on non-x86 platforms. Also add a release checklist entry noting that Linux ARM64 packages build again.
Expand manual release bump options and rename the third-component bump from “serial” to “patch”. Update GitHub workflow choices, docs (BUILDING.md, TECHNICAL.md, release-completion.md) to reflect explicit `auto`, `major (x..)`, `minor (.x.)`, and `patch (..x)` options. Revise tools/build/openq4_release_version.py to normalize aliases, parse new bump modes, rename serial->patch fields and formatting, and implement major/minor/patch handling in change analysis and next-version emission.
Replace pointer-punning and platform-sized unsigned long usage with a memcpy-based idMath_FloatBits returning unsigned int, and update all FLOAT_/IEEE_* macros and integer sign macros to use it (and static_cast<unsigned int> where appropriate). Adds explicit unsigned literals. This removes undefined behavior and dependence on sizeof(long), fixing 64-bit Unix/macOS crashes in collision/physics initialization and hardening related trace/bounds helpers. Also add a release note entry documenting the change.
Switch Linux packages and docs to default to the SDL3 backend and enable native Wayland via SDL3/Wayland/EGL. Update Steam Deck launcher to only force XWayland when no SDL_VIDEO_DRIVER/SDL_VIDEODRIVER is set. Add desktop launcher installer and several VMware helper scripts. Expand joystick mappings in default.cfg and add new HUD GUI assets. Update numerous platform, SDL3, meson and build scripts/source files to support the SDL3 migration and platform improvements.
Add Linux ARM64 validation to CI and improve Linux release symbol handling. Workflows: extend commit/push validation to run additional Linux ARM64 tests, add a linux-arm64 job to commit-validation and an ARM64 matrix entry to push-verification, and make runtime smoke optional per-job. Release: switch Linux/macOS packaging to meson buildtype=debugoptimized with b_ndebug=true, extract detached Linux debug-symbol archives during the manual release job, and upload them as release artifacts. Docs & tests: update BUILDING.md and README to document the new Linux packaging/symbol policy and SDL3 notes, add a long-form Linux compatibility plan, and add several new test scripts under tools/tests. Also include supporting source/workflow tweaks for Linux/posix/sdl3 compatibility and CI robustness.
Add a desktop SDL system console and splash (posix_syscon.cpp) for Linux/macOS, integrate console lifecycle into posix main, and route stdout/stderr text into the console window. Implement GLX swap-control probing and application so r_swapInterval is applied and reported (GLX_EXT/MESA/SGI support, normalization, and apply-on-init/swap). Harden renderer shutdown/restart: clear active render textures, shutdown render-target owners (post-process, shadow maps, frame data, GPU timers, resource tables) before purging images and tearing down GL context, and avoid GL calls when GL is not initialized. Add safe shutdown for background downloads in the filesystem and make curl progress respect thread stop requests. Remove special-case persistent-image skip in image purge. Add meson install of splash bitmap, new linux_vsync_support test and wire it into CI validation/push workflows, and update docs to reflect VSync/SDL3/console improvements. Misc: posix public API additions and minor build/script tweaks.
Added a Discord badge to the README for community engagement.
Standardize branding by changing occurrences of "OpenQ4" to "openQ4" across localization and GUI string files and clean up minor whitespace/formatting issues. Update GitHub Actions to reference and clone the correctly-cased openQ4-GameLibs repo/URL. Also add/update platform resources and packaging files (icons, macOS Xcode/resource files, Windows NSI, installer scripts) and apply various small build/tooling and docs edits to align with these changes.
Add software GL packages and LIBGL_ALWAYS_SOFTWARE to CI workflows, and upload renderer validation artifacts (retain hidden files). Extend push/commit workflow dependency lists and artifact publishing in commit/push verification. Update meson.build to support ObjC/ObjC++ args and Darwin-specific engine/dedicated executable entries. Improve renderer validation tooling: record per-case logs, point result.log to saved case logs, and print failure details; update CI coverage checks to expect the new dependencies/artifacts.
Allow the renderer to boot when the shipped '_default' material decl is missing by attempting a generated internal fallback before hard-failing. R_InitMaterials now logs the fallback attempt, calls FindMaterial("_default") without the strict lookup, and only fatal-errors if the fallback is also unavailable. The release notes were updated to state the ARM64 CI smoke path remains bootable even when stock PK4 materials are absent. Added a CI validation helper (validate_assetless_renderer_bootstrap) that checks for the new fallback logic and strings in RenderSystem_init.cpp and wires it into the linux_arm64_ci_coverage checks.
Add an x86-64 SSE2 idSIMD processor and wire it into idSIMD (new Simd_SSE2.{h,cpp}, Simd.cpp changes), restoring a selectable SIMD path controlled by com_forceGenericSIMD. Implement multiple renderer-side performance optimizations (redundant legacy-backend GL call filtering, buffer-bind shadowing, per-surface hoisting and early-outs) gated by r_useRedundantStateFiltering, plus front-end stable radix sort and shadow-policy short-circuiting. Introduce loading-screen continue input plumbing (Common.{h,cpp}, Session.cpp) and a CI test/tools script (tools/tests/loading_continue_input.py) with workflow updates to run it. Update TECHNICAL.md and release docs to document the new CVars and validation; include small test/fix improvements to idSIMD test handling.
idSoundHardware_OpenAL::Init() called common->FatalError when alcOpenDevice/alcCreateContext/alcMakeContextCurrent failed, killing the engine on machines without any audio backend (e.g. the headless Linux ARM64 CI runner, where PipeWire and ALSA are both unavailable). This aborted the renderer-default-safety-selftest runtime smoke before it could print its results. Degrade gracefully instead: warn, release any partially created device or context, and force s_noSound 1 - matching the existing behavior of the legacy Linux sound backends. The runtime sound paths already guard on s_noSound and a NULL device/context. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Performance pass: enable index buffer VBOs by default (r_useIndexBuffers=1) and add a VBO name pool (r_rendererUploadBufferPool) to reduce gen/data/delete churn; implement pooled allocation/drain logic and stats. Add an SSE2 implementation of DeriveTriPlanes with a bit-exact RSqrt path and re-enable its test. Fix vertex/index cache lifetime issues (use FreeVertexCache in BSE, avoid freeing TAG_TEMP blocks, and make frame-local copies for packed ambient tris to prevent recycled-header aliasing). Introduce a light-grid availability latch to skip provably-dead per-surface work, cache RendererBootstrap promotion-state inputs to avoid repeated memsets, and gate two-sided stencil on the core glStencilOpSeparate+wrap ops. Small fixes: joystick look inversion/axis sign correction, ScenePackets clear optimization noted in plans, and accompanying docs and perf plan added. Overall goal: reduce front-end/submit/backend CPU work and GL churn for measurable frame-time improvements.
Add SSE2 implementations for float-vs-constant compares (CmpGT/CmpGE/CmpLT/CmpLE) with bitnum variants and extend TestCompare with edge-case coverage and calls in Test_f. Introduce R_BindTextureForDirectAccess and replace raw glBindTexture uses in image upload/copy/sampler paths so the per-TMU redundant-bind tracker stays coherent, fixing intermittent SSAO near-white frames. Make index-VBO usage tiered and revert the default to 0 (never); add R_StaticIndexCacheAllowed and gate static index VBO allocations across the renderer to avoid per-frame VBO churn and frame-pacing regressions (modes: 0=never, 1=static-only, 2=all). Hoist decal stage-register evaluation to avoid re-interpreting identical registers per triangle, and add a dormant-pipeline latch + zeroed-stat mirror handling in ModernGLExecutor/tr_backend to reduce redundant bookkeeping on skipped frames. Minor header and API updates to declare the new helpers.
Replace rdtsc-based clock ticks with a monotonic nanosecond clock and add a display-only Sys_GetApproximateProcessorFrequencyHz across platforms. FPS overlay now counts frames against the wall clock and updates every 250 ms to avoid noisy per-frame averages at high refresh rates. SetMachineSpec and processor summary use the new CPU-frequency helper instead of treating clock ticks as CPU speed. Added platform implementations (Linux parsing /proc/cpuinfo, macOS sysctl, Windows export, stub return) and updated headers/docs (including high-framerate plan and AGENTS.md) to note cross-platform considerations and the frequency-scaling fix that resolves mismatches with external frame-rate monitors.
Introduce a generic Con_DrawSizedStringExt and use it to replace redundant small/big string draw paths; remove old Con_DrawBigChar/Con_DrawBigStringExt in favor of the sized API. Add SCR_DIAGNOSTIC_TEXT_SCALE and helper functions for diagnostic padding/line advance, and update SCR_DrawTextRightAlign and SCR_DrawFPS to render top-right diagnostic overlays at a reduced (half) scale and compute layout using the new sized metrics. Also update docs-dev/release-completion.md to note that diagnostic overlays now draw at half size to be less intrusive.
A four-stage multi-agent audit (find, adversarial verify, implement, adversarial review) over src/renderer confirmed 67 findings and landed 66 fixes plus review follow-ups. Rendering correctness: - Shadow-map render passes save/restore GL_STENCIL_TEST; the first mapped light no longer disables stencil shadows for every later light in the view - GLSL interaction prepare no longer stores frame-temp index caches on heap-owned tris (stale/freed vertex-cache header dereference) - CopyFramebuffer/CopyDepthbuffer scratch FBO names are purged before context destruction instead of aliasing new-context render targets after vid_restart - Modern GL state cache is invalidated at backend frame begin; modern-legacy handoffs invalidate the legacy buffer-bind shadow and scissor tracker - Vertical gaussian blur offsets use viewport height, not width - Clustered lighting: CPU depth-slice binning floors like the shaders, and light culling respects the real grid far plane instead of a dead constant - Packed MD5R: weapon/model depth-hack projection applied (math shared with the classic path via R_GetDepthHackProjectionMatrix), binormal validation matches the bind condition, stale PP_LIGHT_ORIGIN fixed for mixed chains - Portal fades keyed per render view instead of frontend viewCount; levelshot-shift frustum vertical extents unmirrored; eyeball deform index Stability and robustness: - GLSL stage program handles stamped with tr.glContextGeneration (bumped only on context creation); FreeData/validate no longer delete stale names into a recreated context nor leak programs after partial restarts - idVertexCache::Init is re-init safe across full vid_restart (headers reclaimed, dead GL names dropped, owner pointers cleared) - Bounds fixes: CropRenderSize, ParseStage stage overflow, RB_ShowOverdraw allocation, simplex glyph index, AreasAreConnected area index - RoQ cinematics: chunk-size guard, corrupt-file decode loop exit, NULL iFile ResetTime crash, deterministic failed-load state - Images: TGA RLE size validation, cube-map load leak and failed-load retrigger, generated bimage names include the mip policy, compressed SubImageUpload height clamp - screenshot/envshot dimension and blend clamps; bounded _alloca in R_MakeAmbientMap_f; reloadSurface null checks; win32 minimize/resize and pixel-format failure handling; demo joint/mapname validation - Self-tests reset their global state through the proper choke points instead of leaving dangling stack pointers and poisoned stats; validation matrix accepts the clean post-selftest shadow state Performance: - Scene-packet record dedup via hash side-tables (was O(packets x records)), lazy shadow pass opening (was a double chain walk), metrics snapshot skipped when disabled, MaterialResourceTable no longer memsets ~2.3 MB per frame, shadow-map receiver binds and cascade uniforms hoisted per pass, motion blur / lens flare depth copies gated, PointInArea descent latched on grid-less maps, ~1 MB packet frame moved off the backend stack Tests: - testSIMD now actually validates Dot, Cmp*, MinMax, CreateShadowCache, MultiplyJoints and TransformVerts (calls were commented out), with new Cmp* tail/NaN edge cases; SSE2 MinMax NaN semantics match generic All 26 automated validation cases pass, testSIMD all-ok, smoke and 60-case shadow benchmark profiles pass; storage1 A/B against the pre-change build shows identical frame cost on identical scene work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multiple fixes and improvements across renderer, framework, UI and docs: - Lens flare GLSL/GL code: add a depthBias uniform and safer sampling (inset by half-texel), introduce SafeNormalizedAxis, preserve additive alpha behavior, remove unused texcoord from VS; improve occlusion/compactness scoring, compute per-light occlusion bias from world-space tolerance, tighten color/intensity clamping, pass pattern axis to quad draws, and adjust flare parameters for better visual quality. - Image/depth handling: allocate real depth images (AllocImage with FMT_DEPTH) instead of placeholder RGBA; ensure CopyDepthbuffer forces depth-renderable internal format when needed and always uploads GL_FLOAT depth data for blits/readbacks. - Map path / entity-filter parsing: robust normalization and stripping of embedded entity filter tokens in idFileSystem and Session; new helpers to split and normalize map path and entity filter, threaded through session/map/command code to preserve/propagate normalized values and si_entityFilter serverinfo handling. - Minor UX/docs: add a shadow-mapping rework plan document and small release-completion wording fixes. - Misc: small whitespace/ordering fixes in draw_common and UI window code. These changes fix incorrect depth storage and copying, improve lens-flare occlusion correctness and stability, and make map/entity-filter handling resilient to embedded filters and extraneous whitespace.
Add high-DPI mouse handling and tests, improve glyph atlas padding/width calculations, and make shadow-map receiver handling more robust. - CI/workflows: include and run new tools/tests/linux_highdpi_mouse.py in commit-validation and push-verification. - Tools: add tools/tests/linux_highdpi_mouse.py. - Editor: add duplicate VSCode launch configs for first/second storage1 and process1 scenarios. - SDL3 backend: replace integer-rounding with fractional remainder consumption for mouse deltas, handle display/scale change events to refresh routing/visibility and invalidate menu mouse routing. - UI/DeviceContext: add per-font guard texel sizing for small fonts, compute glyph visible right edge, pass font to PaintGlyph, and fix TextWidth/CharWidth to account for glyph guards and visible edges; update font parity self-tests to match new behavior. - Renderer: route soft-particle BSE effects to legacy path when required (draw plan/executor/common), add shadow-map receiver eligibility/filtering, filtered draw interaction submission, receiver-fallback accounting and reporting, and ensure prepare-failure conditions block mask success to avoid partial/misleading shadow submissions. - draw_common: ensure current depth copy flag reset for legacy feedback path. These changes aim to stabilize high-DPI mouse feel, prevent glyph clipping on small HUD fonts, and avoid dropping mapped shadows for compatible receivers while providing clearer diagnostics and stats for shadow fallback cases.
Multiple fixes and improvements: clamp lens-flare depth sampling to valid captured texels and skip flare when no depth capture is available (prevents edge shimmer/stale-depth leaks), and add an early-return guard after requesting depth capture. Add VSCode launch configuration for an MCC landing lift test. Rework machine-spec detection: apply an AMD CPU adjustment, centralize detection thresholds/messages, and auto-repair unset com_machineSpec (marking cvars for archive when repaired). Modern shadow planner: introduce a budget-miss policy helper and use it when tiles are throttled. Renderer ARB2: add a receiver-fallback result and report receiver-fallbacks instead of treating them as mask-fail/stencil fallbacks. Update release notes to reflect these changes.
Extend Linux ARM64 CI validation and renderer args to cover assetless game startup (AAS) support. Update docs to mention AAS declarations. Change renderer_validation_matrix to enable g_allowAssetlessStartup when skipping official pak validation. Add GAME_LIBS repository handling and a new validate_assetless_game_bootstrap() test that inspects game/mpgame sources (Game_local.cpp and SysCvar) for AAS lookup, g_allowAssetlessStartup guards/logs and cvar declarations, and wire it into the CI checks. Adjust a couple of string checks in existing validation logic to match the new flags.
Scale joystick look axes from raw -127..127 to normalized [-1..1] before applying look sensitivity. Adds JOYSTICK_AXIS_LOOK_SCALE and converts look axis variables to float in JoystickMove to prevent excessively fast Xbox-style right-stick turning. Updates docs to reflect that Look Sensitivity is applied after normalization and adds a troubleshooting tip for "Aim feels too fast."
Enables openQ4's existing Vulkan renderer module to run on macOS through MoltenVK, a Vulkan-on-Metal translation layer, as an opt-in r_renderApi vulkan selection bundled inside both existing macOS packages. OpenGL remains the default and recommended macOS renderer in both package variants. MoltenVK is pinned to v1.4.1 (the newest release compatible with the macOS 11 floor). No third package variant is added. Key changes: - Remove the darwin carve-out from build_renderer_vk; darwin now builds renderer-vk_<arch>.dylib as a shared_library with hidden visibility and a GetRenderAPI-only export list - Add VK_KHR_portability_enumeration/VK_KHR_portability_subset negotiation, Vulkan 1.3 floor check, maxBoundDescriptorSets guard, and R5G6B5 fallback probe for Metal-backed devices - Add VK_Device_InitLoader() and SDL3_PinBundledMoltenVKLibrary() so the engine (SDL) and renderer module (volk) resolve the same MoltenVK image - Add tools/build/prepare_macos_moltenvk.sh for pinned acquisition, verification, and staging of libMoltenVK.dylib - Add tools/build/macos_renderer_module.exp (export list: _GetRenderAPI only) - Extend packaging, universal2 assembly, signing, and archive validation for the renderer module and MoltenVK runtime - Add tools/tests/macos_moltenvk_policy.py pinning the portability contract, loader agreement, module export, MSL-hostile shader constructs, and translation-layer wording - Add decision gate docs/dev/macos-moltenvk-decision.md and provider policy docs/dev/macos-moltenvk-provider-policy.md - Wire new test into CI workflows and openq4_validate.py
Addresses GitHub issues #78, #79, #82, #87, #88 and #89. Startup failures exited silently (#79). idCommonLocal::Error's recursive fatal branch called Sys_Quit(), which reports EXIT_SUCCESS and prints nothing, so the original message never reached the log and the shell saw a clean exit. What tripped it: idVertexCache::PurgeAll ran before Init() linked its list sentinels and handed ActuallyFree() a NULL block, raising a second error while the first was still unwinding. Both are fixed, and the glConfig.isInitialized guard at the call site is gone - it was set before vertexCache.Init() ran and cleared by ShutdownOpenGL() without freeing the cache, so it was wrong in both directions. The four startup content checks now print the resolved paths and the pk4 files actually found, which is what identifies the common Linux cause of a case-mismatched directory or pk4 name copied from a Windows install. Accented text rendered as two wrong glyphs (#89). The string tables are UTF-8 while the stock fonts are a fixed 256-glyph atlas indexed by a raw byte, so "MENUS" drew as "MENA S". idLangDict::Load now converts a UTF-8 table to the font codepage, folding the typographic characters that land on a zero-advance .notdef cell to ASCII - a non-breaking space would otherwise delete the word gap rather than merely draw nothing. CP1252 and ASCII tables are left byte-identical, so retail localized packs are unaffected. This also fixes mojibake apostrophes in English subtitles. DDS replacements were rejected without explanation (#82). ImageTools_SetCompressionCaps had a single caller, and imagetools is a static library linked separately into the engine and each renderer module, so depending on the active renderer the capability block stayed zero-initialized and every DDS was refused. Caps are now published from both backends and mirrored into the engine's copy. User replacements are probed ahead of the retail progimg/ tree instead of behind it, BC7 normal maps use the ordinary XYZ-in-RGB layout rather than being forced into the DXT5nm alpha convention, the ultra preset no longer zeroes the cvar and discards an archived setting, and a miss now logs every candidate path with the reason it was rejected. Sound lookup failures were invisible (#87). A sample that resolved to nothing was replaced by the default with no warning. The engine now reports the sample and every path probed, voice-over falls back to English when a language ships text but no voice track as the original game did, and s_showVoices prints its table instead of building one every frame and discarding it. The reported symptom is not yet root-caused; these are the confirmed defects on that path. Scripted characters could stall a map permanently (#88). idAASFileLocal::FinishAreas never derived aasArea_t::ceiling, so AreaCeiling() returned 0 for every area on every map and consumers computing "ceiling - bounds height" clamped every flying goal below ground. Movement failures were also silent: with no AAS, MoveToEntity() still returns true and the AI wanders, so the map script waits on scriptedDone() forever. Both failure modes now warn, and ai_scriptedMoveTimeout (30s, 0 to disable) abandons a move that has made no progress so the script can continue. Loading a savegame over a live map left stale state (#89). UnloadMap() did not stop the game sound world before destroying the entities that own its emitters, and a freed emitter is only recycled once its channel list empties, so looping sounds from the outgoing session survived. Stop() already did this, which is why quitting to the menu first was clean. Hardened the arm64 paths behind #78. The savegame root cause is already fixed; a regression guard now pins it. The binary lexer's stored-size defaults fail closed instead of asserting - release builds compile asserts out, leaving unreadSize at 0 and desynchronizing the rest of the stream - an unexpected BC7 image degrades to uncompressed instead of ending the session mid-load, and -ffp-contract=off keeps ARM64 and x86-64 evaluating the same float math. Requires the companion change in openQ4-game for the AI and AAS reporting, the idlib string-table mirror, and the matching build flag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix six non-macOS bug reports (#78, #79, #82, #87, #88, #89)
Ports several Quake Live multiplayer gametypes to openQ4: - Add scoreboard.gui and summary.gui GUIs for the multiplayer scoreboard and end-of-match summary screens - Add match progression HUD widgets to mphud.gui (round text, round timer, overtime, ready count) - Add localized string entries (#str_413xx–#str_414xx) for new gametypes, match progression messages, round-based mode messages, and HUD labels across all four language files - Add design/planning document for the Quake Live multiplayer port New string blocks cover gametype names (Duel, Clan Arena, Freeze Tag, Red Rover, Overload, Harvester, Domination, Attack & Defend), match state messages, round-based mode events, Freeze Tag and Red Rover gameplay messages, and HUD labels.
Since 9adb69a (first shipped in v0.9.0) R_BuildDDSCandidates offered the retail progimg/ tree as a DDS replacement for any plain source name under gfx/env/, on the assumption that "retail cube sides are stored as plain progimg/gfx/env/... DDS files". They are stored there, but not as sources: progimg/ holds Raven's engine OUTPUT, so each cube side already carries the camera -> native conversion R_LoadCubeImages performs. Verified against the stock paks for act_2, canyonclouds, red and stars - every face decodes to its .tga transformed exactly the way the loader is about to transform it again (forward/up/down transposed, left vertically flipped, right horizontally flipped, back transposed and flipped both ways). R_LoadCubeImages therefore ran the conversion twice and every affected skybox came out mis-oriented, on all three renderers, since the image code is shared. Retail never read those files back for cube maps - id's "FIXME: precompressed cube map files" - and always re-derived the faces from the .tga sources. Drop the gfx/env/ special case so plain sources stop resolving into progimg/, and suppress the retail candidate outright while R_LoadCubeImages is loading faces so an image-program face name cannot reach it either. dds/ packs and .dds files beside the source (Quake 4's own gfx/env/mp_sky/, which decodes identical to its .tga) are authored in source orientation and stay eligible. Cube faces are assembled before they reach the generated file, so a machine that already cached a doubly-converted skybox kept it: the pk4 sources it names never changed. GetGeneratedName now tags cube entries with a conversion revision, which retires those caches without touching 2D ones. Verified on game/airdefense1 (gfx/env/act_2): before, the sky cube loaded with no DDS-replacement miss logged for any of the six faces - all silently taken from progimg/. After, all six report no replacement and the retail candidate is gone from the probe list, so the faces come from their .tga sources. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
image_writeGeneratedImages was introduced defaulting to 0, which gates idBinaryImage::WriteGeneratedFile. No generated/ tree ships in pak0 and com_productionMode is 0 for players, so the binary image cache was never populated for anyone: every load re-decoded and re-compressed the same sources on the CPU and discarded the result. The performance presets also pinned the cvar to 0, so picking any quality level re-disabled the cache regardless of the default. Only the CPU decode/compress path reaches WriteGeneratedFile - images served straight from a precompressed DDS bypass the generated file entirely - so this caches exactly the expensive sources and stays small on retail data. That is also why the symptom was uneven across players: with retail progimg/ DDS available the loss is a couple of seconds, but with no usable DDS fast path (no S3TC, image_usePrecompressedTextures 0 or 2, cube maps, custom content) the whole set takes the CPU path on every single load. Steady-state loads of maps/game/mcc_1, optimized build, retail data: retail DDS available 10.5s -> 7.8s (image phase 5.1s -> 3.5s) no DDS fast path 19.1s -> 6.4s (image phase 14.2s -> 1.8s) Cache cost is ~17 MB per map on retail data and ~550 MB per map when everything goes through the CPU path, with a separate copy per downsize signature. Presets now set the cvar to 1 so a quality change writes the new downsize variants instead of disabling caching, and the preset self-test expectation moves with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Common_DetectMachineSpec gates its zero-frequency escape hatch on `#if defined( MACOS_X ) && ( __aarch64__ || __arm64__ )`, but no arm64 host reports a usable CPU frequency: Apple Silicon has no hw.cpufrequency, aarch64 Linux has no "cpu MHz" line in /proc/cpuinfo, and Windows on ARM has no "~MHz" registry value under HARDWARE\DESCRIPTION\System\CentralProcessor. Sys_GetApproximateProcessorFrequencyHz returns 0.0 on all three, and every generic tier below requires ghz >= 2.29f. So on Linux aarch64 and Windows ARM64 a fresh install classifies as machineSpec 0 and archives it: image_downSize 1, anisotropy 1, no MSAA, no post-AA, r_screenFraction 85, degraded sound-shader mixing - on Graviton/Ampere/Snapdragon-class hardware. Widen the guard to every arm64 target so those hosts classify by memory like Apple Silicon already does. Sys_GetCPUId on Windows returns a hardcoded CPUID_INTEL. That value flows into win32.cpuid, Sys_GetProcessorId, idSIMD::InitProcessor and SetMachineSpec, and makes the engine print "Intel processor" on Snapdragon hardware whenever the registry name string is unavailable. Return CPUID_GENERIC on ARM64, matching what the Linux backend already does on aarch64. SIMD dispatch is unaffected because Simd.cpp keys off the compile-time architecture macro, not this value. The crash-log CONTEXT dump only has _M_X64 and _M_IX86 branches, so a Windows ARM64 crash log carries no register state at all, and GetCallStack is stubbed out on win32 - the minidump is the only remaining evidence. Add an _M_ARM64 branch printing Pc/Sp/Fp/Lr, Cpsr/Fpcr/Fpsr and X0-X28. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
idCmdSystem::ArgCompletion_FolderExtension takes a variadic run of const char* extensions closed by a sentinel, and all 13 call sites in CmdSystem.h pass NULL. Under MSVC C++ NULL expands to plain integer 0, so only four bytes of the argument are defined. On x86-64 that is harmless: the sentinel lands in a register and the upper half is zeroed. On AAPCS64 and Windows ARM64 these calls have enough variadic arguments to spill past the register save area onto the stack, where only the argument's own size is guaranteed - the upper four bytes of the slot are whatever was there before. The receiver at CmdSystem.cpp then reads that as a const char*, so a garbage pointer is treated as a valid extension string and the loop walks past the end of the argument list. Use nullptr, which is pointer-sized by definition. The last named parameter before the ellipsis is also a bool, and va_start on a parameter subject to the default argument promotions is undefined behavior. Every current compiler does the right thing, but AAPCS64's split general/vector register save area is a materially different va_start implementation from the x86-64 SysV one this code was written against. Make it an int. The parameter is passed in a register either way, so the vtable slot and the calling convention are unchanged. Mirrored into openQ4-game in lockstep, including the four call sites in the game and mpgame trees. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Plain char is signed on x86-64 for all three platforms, and signed on Apple arm64 and Windows ARM64, but GCC and Clang default it to *unsigned* on Linux AArch64. Nothing in either repo's meson files pins it, so the same source produces different behavior on the one arm64 platform that has CI. idStr::Hash and idStr::IHash fold the character straight into an int accumulator, so a byte such as 0xE9 contributes -23 on x64 and +233 on Linux aarch64. That gives the same string different idHashIndex, idHashTable and sound-sample-cache buckets, which changes collision-chain traversal order between architectures. Read through byte and accumulate in uint32_t, matching how idStr::FileNameHash was already hardened; this also removes the signed-overflow undefined behavior in the original accumulator. All consumers mask the result, so nothing observable changes on x64. idStr::Cmp and the whole Cmpn/Icmp/Icmpn/IcmpNoEscape/IcmpPath/IcmpnPath family load into int through char, so collation of any byte >= 0x80 orders before the ASCII range on the signed targets and after it on Linux aarch64. That matters beyond sorting: idDict::Checksum sorts its keys with idStr::Cmp, and that checksum is written to the wire in AsyncServer and compared in AsyncClient, so a divergence is a permanent userinfo-mismatch resync loop rather than a cosmetic ordering difference. Dict keys are ASCII in practice, so this is a latent rather than active break, but the fix is to read through byte in all nine sites and get byte-value ordering everywhere. The ctype functions are only defined for values representable as unsigned char and for EOF; passing a negative char is undefined. Cast at the call sites that pass a possibly-negative char - the toupper comparisons in idStr::Filter and idStr::FindText, the auto-complete fold in EditField, the MD3 surface-name lowercase, and the ChoiceWindow shortcut match - matching the casts LangDict.cpp and win_shared.cpp already carry. These have to move with the hash/compare change because pinning char signedness later changes the domain they see. Mirrored into openQ4-game in lockstep, including the game and mpgame copies of Anim_Blend, Camera and LipSync. Verified: full x64 build clean, and the SP gameplay smoke loads and runs game/storage1 from retail data with no new warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The POSIX backend takes the critical section in Sys_CreateThread, Sys_RemoveThreadInfo and Sys_IsCurrentThreadStopRequested. The win32 backend locks none of them, and the renderer-gl module's own registry locks only its remove path even though the lock is already forwarded in that file. Publishing a thread is two separate stores - g_threads[i] = &info and g_thread_count++ - and every reader walks the array up to the count with no lock. On x86 TSO the stores retire in order, which is why this has never fired. ARM64 is weakly ordered and may make the bumped count visible first, so a worker polling Sys_IsCurrentThreadStopRequested every loop iteration (the background download thread, every light-grid bake worker) can read the new count and dereference an uninitialized slot: an access violation during map load. Sys_RemoveThreadInfo can also leave NULL slots behind while another thread is mid-walk. Take the critical section in the win32 Sys_CreateThread, Sys_RemoveThreadInfo, Sys_IsCurrentThreadStopRequested and Sys_GetThreadName, and in the renderer module's _WIN32 Sys_CreateThread and Sys_IsCurrentThreadStopRequested, so both registries match the POSIX contract. The critical sections are initialized in WinMain before Com_Init, well before any thread is created. Sys_GetThreadName was additionally dereferencing g_threads[i] with no NULL guard, unlike Sys_IsCurrentThreadStopRequested right above it. Add it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
idSIMD::InitProcessor only installs a specialized processor on architectures that have one. ID_SIMD_SSE2_AVAILABLE is defined only for x86 and x86-64, so on every arm64 target both arms are dead and processor stays NULL. Test_f then sets p_simd = generic = p_generic, and every comparison in TestDot, TestCompare, TestMinMax, the TransformVerts cases and TestCreateShadowCache compares a buffer against itself - including the tail, NaN and negative-zero edge blocks. A bring-up engineer on Apple Silicon or Windows on ARM gets a full page of green that validates nothing, and it would stay green if a NEON processor were added incorrectly. Say so explicitly when the two processors are the same object. The benchmark clock on the non-Windows, non-macOS path was `start = 0; end = 1`, a constant single tick, so every SIMD benchmark on Linux printed the same meaningless number. That is the only arm64 platform with CI, which makes it the one place a future NEON port could actually be measured and regression-tested. Use CLOCK_MONOTONIC, the same source Sys_GetClockTicks in src/sys/linux/main.cpp already uses and documents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renderer modules are shipped package members loaded into the client process, but they were not in RUNTIME_BINARY_PATTERNS, so neither the static-CRT enforcement nor the mixed-architecture guard in tools/build/windows_runtime.py could see them. A stale renderer-gl_x64.dll sitting next to an arm64 client passed every check. Add both module patterns; the collector globs, so builds without them are unaffected. Confirmed by a staging run: the modules now appear in validated_binaries. The staged-payload validator in manual-release.yml was gated to `matrix.platform == 'linux'`, so Windows x64 and arm64 release packages were produced with only an inline "baseoq4 is non-empty" check - no arch-set consistency, no distinct SP/MP module check, no PDB matching. Widen it to every non-macOS platform (macOS has its own inline suite). With --skip-build, --install only requests validation of the already staged tree; it does not re-run meson install, so nothing that was staged earlier in the job is disturbed. Both arm64 support tiers can only graduate on operator-supplied hardware evidence, and .github/ISSUE_TEMPLATE carried only the macOS template. Add Linux ARM64 and Windows ARM64 report forms that collect what the evidence records actually need: exact artifact, physical-vs-emulated, distro or Windows build, display session, GPU and driver, whether the same thing happens on x64, and the reported machine-quality tier. The Windows form also asks which binary was run, since Windows on ARM transparently emulates x64, and names the two known rough edges up front - the OpenGL default on a platform with no desktop-GL ICD, and the separately prepared ARM64 OpenAL runtime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to 2709cf5, which widened Common_DetectMachineSpec's zero-CPU-frequency escape hatch from Apple Silicon to every arm64 target so arm64 hosts stop classifying as machineSpec 0. The mechanism was right and the fallback was not: with no VRAM probe the branch substitutes sysRam / 2 for vidRam, so an 8 GB board yields effectiveVidRam 4096 and sysRam 8192, clears `>= 300 && >= 1000`, and lands on machineSpec 3. That is the wrong direction for exactly the hardware that has been failing. Tier 0 is the only tier that sets image_downSize, image_ignoreHighQuality, image_downSizeSpecular and image_downSizeBump (Com_ExecMachineSpec_f), and openQ4 currently uploads ordinary textures uncompressed, so leaving tier 0 multiplies the texture budget - and tier 3 additionally asks for r_mode 5, r_multiSamples 8 and image_anisotropy 16. Both arm64 hosts that have reported memory failures were 8 GB boards killed during load: issue #76 on a Raspberry Pi 5 ("it started creating some images, at the end i ran out of ram") and issue #78 on aarch64 Linux (OOM-killed after a 10-20 minute load). Detection only runs when com_machineSpec < 0, so this fired on precisely the fresh-install case those reporters hit. sysRam / 2 is a guess about system memory, not evidence about the GPU: an 8 GB single-board computer and a workstation produce the same number. So split the two cases. A real VRAM probe keeps the stock thresholds - that is the case the original change was aimed at, such as a Windows on ARM laptop that reports a GPU. A guessed one now requires 12/16/24 GB for tiers 1/2/3, which leaves 8 GB boards on tier 0 exactly as before 2709cf5 while still promoting a genuinely large host. Apple Silicon keeps the stock thresholds unconditionally. It has real unified memory on a GPU class known to run the game well, no arm64 Mac has reported a memory-pressure failure, and that path predates 2709cf5 - this restores its behavior byte for byte. Note this leaves tier 0's r_screenFraction 85 in place, which two independent reporters (issue #49 and NeonKnightOA) say produces visible colour corruption at any value below 100. That is a separate defect in the tier-0 preset and is not addressed here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the scalable TrueType font system with accessibility text backing, cel-shading and player-outline renderer paths, themed system consoles on win32 and posix, embedded UI icons, picmip policy work, and the runtime navmesh bot plus client combat-effect fixes for multiplayer. Extends the image pipeline, renderer caps and ARB2 corridor accordingly, with the matching docs, localized strings and token-pinning validation tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> @
The char-signedness commit aa477f2 saved Str.cpp as UTF-8, which rewrote all 401 Latin-1 high bytes to U+FFFD. In the braced initializers for idStr::printableCharacter, upperCaseCharacter and lowerCaseCharacter each '\xC0' therefore became the three-byte EF BF BD sequence - a multi-character literal worth 15712189. MSVC accepts that with a warning, so Windows x64 and ARM64 kept building, but GCC and Clang reject it as a narrowing conversion and every Linux and macOS job in the 0.9.1 release run failed in "Build and install" before producing an artifact. Restore the 40 affected lines from the pre-corruption blob. Each one is exactly the old line with every byte >= 0x80 replaced by U+FFFD, so the restoration is mechanical rather than a guess, and the recovered tables come out byte-identical to the openQ4-game copy of idlib, which never got re-encoded. Lexer.cpp lost its six high bytes the same way much earlier, in 3eedc9b. Those are string literals rather than braced initializers, so nothing failed to compile - instead both punctuation entries became the *same* mangled sequence, which made P_INVERTED_QUERY unreachable in the table, in WriteBinaryToken and in ReadToken. Write these as the ASCII escapes "\xa1" and "\xbf": the compiled bytes are identical, but the file stays valid UTF-8 so it cannot be flattened again, and the tooling that reads it as strict UTF-8 keeps working. Str.cpp keeps its raw high bytes - the comment columns carry them too, and its only reader already decodes with errors="surrogateescape". Add tools/tests/source_charset_integrity.py to catch both shapes: any U+FFFD in a tracked C/C++ source, a multi-byte character literal in the Str.cpp tables, and the Lexer punctuation escapes drifting or collapsing onto each other. Wire it into the validation runner and both CI workflows. Verified it fails on a re-corrupted Str.cpp with the exact signature of this incident (401 sequences, first at line 112) and on a simulated silent Lexer collapse. Verified: full x64 build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten registered Python validation tests were failing on main, so CI carried no signal even though releases were unaffected (manual-release.yml passes --skip-python-tests). Nine were stale pins left behind by the renderer module split and one was a real defect the pin correctly caught. Real fix: - vk_GuiExecutor.cpp null-tested three vertexCache.Position() results and then used them as pointer bases. Position() returns a byte offset, not an address, when the cache is VBO-backed, so offset zero is legal: the null test silently dropped a valid decal color slice and the arithmetic formed the address in pointer space. All three sites now match the GL back end (draw_common.cpp) and go through RB_DrawVertAttributePointer. Pins that followed moved code, not relaxed invariants: - The SDL3 GL context half moved to src/renderer/OpenGL/gl_ContextSDL3.cpp and now reaches SDL through the window-services seam. hdr_postprocess_math (also covering the Vulkan module's copy), linux_vsync_support and renderer_msaa_cvar_safety pin both halves so neither side can drift. - GLimp_Init's window/input startup moved into PrepareWindowSystem and CreateWindowForFramebuffer; linux_highdpi_mouse, sdl3_multidisplay_windowing and steam_deck_support follow it there, plus the SDL3_SetVidSize / SDL3_SetUIViewport indirection and the module's glConfig poll (previously masked behind the first failure). - release_tooling_safety tracks the step renamed in 0d96080; the ordering invariant it guards is unchanged. - linux_dedicated_server_smoke_contract pins the two defines the dedicated GLEW variant needs instead of the exact argument list, which grew -DGLAPI=extern in 0c9d76e. Gap the tests found: - vk_shader_header_pin.py was never wired into local validation or either workflow. Registered in all three; it skips cleanly without glslang. Verified: the 94 registered tests now pass except renderer_validation_matrix (needs a staged client build) and mp_bot_navigation (fails on uncommitted openQ4-game work, which grew rvBot::UpdateGoal past the test's scan window). vk_GuiExecutor.cpp type-checks under the renderer-vk flags with the pointer-truncation warnings promoted to errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace stb_vorbis with libvorbisfile (libogg + libvorbis Meson wraps) for Ogg decoding with proper hole recovery and EOF padding. Add a complete 19-character Prey multiplayer bot roster: six play styles (rusher, sniper, roamer, hunter, ambusher, skirmisher), mechanics files, and voice banks with 14 events × 8 lines and 9 reply categories each. Characters map to retail ui_modelNum slots 0–18. Navigation is a gravity-aware combat fallback; full map routing remains deferred. Post-closure rebase hardening: FW-19 render-demo magic with compile-time equal-length guards, FW-38 network rate defaults (32000), WP5.7 GUI stream marker/version with no-overlap window-flag assert, RD-22 surfIndex struct guard, loading-music decl-load scope guard, missing autosave string #str_107240, roadhouse_quick fixture relocated to content/basepr/dev/, plain Meson install cleanup script, ARB program lazy-load for dormant Quake 4 records, particle deform implementation, image alias/suppression improvements, and BSE API renamed to openPREY_*. CI now builds engine + dedicated + GL/Vulkan renderers + unified game module on Windows and Linux. VSCode launch configs split into OpenGL/Vulkan variants with s_deviceName default. Deathwalk maps added to manifest.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
blackletum
pushed a commit
to blackletum/OpenPREY
that referenced
this pull request
Aug 5, 2026
…hadow-sampling-for-stability Harden projected shadow coordinate validation and add debug visualization
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.
No description provided.