# openQ4 Release Completion List

Use this file as the source list for release changelog entries.

Process:
1. Add completed work under "Ready For Changelog".
2. When cutting a release with curated notes, save them in `docs/dev/releases/vX.Y.Z.md`; the manual release workflow will use that tracked file instead of the auto-generated history summary.
3. If no tracked release file exists, the workflow falls back to the generated release notes from commit history.
4. Move shipped items into a historical release section here (optional), and keep remaining work in "Carry Forward".
5. For macOS support claims, complete the "macOS Evidence Gate" below and keep `docs/dev/macos-signoff-evidence.md` current before promoting anything beyond experimental wording.

## macOS Support Claim Guard

Use this checklist before editing curated release notes or front-door package docs that mention macOS.

- [ ] Release notes that mention macOS say "experimental Apple Silicon/arm64 macOS" unless a completed evidence entry supports stronger language.
- [ ] Any first-class, stable, or fully supported macOS claim cites the current release entry in `docs/dev/macos-signoff-evidence.md` and has the "macOS Evidence Gate" completed.
- [ ] Intel Mac, universal2, and Rosetta appear only as unsupported, not-published, or future-policy items until a separate matrix expansion plan is accepted.
- [ ] `macos_graphics_bridge=metal` is described as a Metal bridge around the OpenGL renderer, not native Metal or OpenGL-free rendering.
- [ ] `platform_backend=native` on macOS is described as comparison-only diagnostic infrastructure, not a release backend.
- [ ] Until GitHub issue #73 is closed, curated release notes include issue #73 as a known experimental macOS Apple OpenGL 2.1/ARB2 startup limitation, or explicitly move it to a named known-limitation section with matching signoff evidence.

## Ready For Changelog

- [x] Experimental macOS ARM64 builds now link the AudioToolbox framework required by SDL3's CoreAudio backend, so both OpenGL and Metal packages can link their client executable instead of failing on unresolved AudioQueue symbols.
- [x] OpenGL renderer robustness is stronger across driver tiers and repeated video restarts: render-target names are tied to their owning context generation, framebuffer validation is removed from the bind hot path and cached per cubemap face, texture-unit binding has core DSA, EXT DSA, and compatibility fallbacks, VAO element-buffer state is tracked correctly, multi-bind fallback is target-aware, upload rings try idle slots before bounded fence waits, debug contexts queue structured driver messages safely, GPU-driven shaders reject invalid buffer indices, and renderer validation fails on hidden GL errors or failed benchmark thresholds.
- [x] The single-player weapon wheel now uses a smoother circular depth-of-field gather with a per-pixel blur radius and depth-edge rejection, reducing star-shaped smears and foreground/background color bleed while keeping the nearby view weapon legible.
- [x] Performance presets now change only real, registered runtime controls: their 1x-to-16x anisotropy ladder drives the renderer instead of being ignored in favor of the hardware maximum, texture downsizing uses typed/range-checked cvars without a duplicate case-only alias, obsolete no-op image settings no longer pollute configs, every applied value is verified, and an unexpected normalization failure rolls the entire preset back instead of leaving a partial profile active.
- [x] Shadow-map correctness and cost rework (phase 0 of the 2026-07-09 shadow plan): the projected caster path now applies a real slope-scale depth offset in the caster shader (the previous `glPolygonOffset` state was silently inert against shader-written depth, which is why no bias cvar combination could resolve acne vs. detachment), point-light casters no longer leave fragment depth undefined on the default path and store slope-biased radial depth in both variants, hardware depth-compare sampling with linearly filtered (hardware PCF) taps is now the default for projected and point paths with PCSS-lite automatically selecting the raw-depth path, caster passes bind their program once per pass instead of per surface, receiver-plane bias derivatives are computed in uniform control flow, the PCSS filter-radius guard uses the correct bound, and `r_shadowMapSkipStencilShadows` (default on) stops generating and linking stencil shadow volumes for lights that will render shadow maps, with a per-light automatic fallback that restores volumes the frame after any shadow-map pass failure or whenever fallback receivers still need stencil shadows.
- [x] SDL3 Windows startup now keeps the splash screen stable through renderer startup: openQ4 sets DPI awareness before creating the Win32 splash window and removes the splash at the render-window handoff, avoiding the brief scale/position jump when the SDL3 window initializes.
- [x] Objective popups now preserve retail simple-window clipping behavior, so long stock objective titles such as "Regroup with Rhino Squad" render fully instead of losing their final characters.
- [x] Savegame handling is stricter and more recoverable: new saves carry a generated engine/GameLibs source snapshot stamp plus payload sync markers and an end footer, the session save header/description and GameLibs payload writers now detect short writes before committing staged files, staged saves must validate their persistent-player dictionaries plus the payload stamp/current build/footer before replacing an existing slot, generated description sidecars and preview TGA headers are validated before promotion, the load path now commits persistent-player state only after the full save header is valid and preflights gameplay compatibility before tearing down the current map, the save/load menu filters malformed save headers and unsafe slot names, description sidecars are read through a bounded parser and must still map back to their save slot, outbound dict/winding/object data is bounded, AI movement path caches are initialized and serialized with bounded live entries, source stamps are stable across Windows and Unix line endings, legacy saves must at least match the current payload build number, mismatched saves restart the level with persistent player data before object restore, and corrupted current-format saves now fail at the nearest restore boundary with a clear marker/footer diagnostic instead of surfacing later as invalid AI path lengths or other drift errors.
- [x] Savegame restore now also bounds SP/MP script interpreter state before rebuilding scripts from disk: call-stack depth, local-stack usage, function indices, statement pointers, stack bases, pop parameters, and multi-frame event names are validated so malformed saves fail at restore with a clear script error instead of indexing fixed interpreter arrays.
- [x] Savegame restore now rejects malformed SP/MP scalar state before it can steer later arrays or script cleanup: script-program watermarks, actor animation channels, client entity numbers, crawl-effect joint indices, crawl directions, and simple animated-entity counters are range-checked during restore.
- [x] Savegame sound and render restore is stricter: sound-world payloads now use checked primitive/string reads and writes with practical emitter/channel caps and sound-class validation, restored sound emitters retain their serialized indices instead of aliasing reusable empty emitters, trailing empty emitters are omitted from new saves, and SP/MP render entities now reject malformed saved bounds in release builds instead of relying only on debug asserts.
- [x] Save construction now hashes SP/MP game-object pointer lookups while preserving serialized object indices, avoiding repeated full-list scans as large maps discover and write object references.
- [x] Savegame UI restore is stricter: GUI window, simple-window, winvar, script, and arcade-minigame payloads now use checked block reads for saved scalar/vector/color state, clear stale child pointers before resolving saved window ids, and reject malformed Bust Out powerup enum values instead of silently continuing with partially restored UI data.
- [x] Savegame physics and mover restore is stricter: SP/MP articulated-figure payloads now reject body/constraint count mismatches in release builds, and elevator restores validate saved state plus floor-table counts before appending restored floor records.
- [x] Experimental macOS release packaging now fails cleanly when required release collateral is malformed: a placeholder or corrupted `collect_macos_support_info.sh` is reported as a one-line package error instead of escaping as a Python traceback before docs, binaries, signing, or archives are generated.
- [x] Experimental macOS signoff evidence recording now reuses the signoff archive validator's bounded text reader when parsing accepted reports and updates the evidence index through a same-directory temporary file plus replace, so oversized/swapped report members and interrupted recordings cannot leave unchecked or partially written release evidence.
- [x] Experimental macOS staged-package validation now rejects symlinked `.install` roots, symlinked `baseoq4/` game roots, and symlinked payload entries before parsing support-collector content, matching the release/archive policy that macOS payloads must be real files and directories rather than hand-staged links.
- [x] Experimental macOS support collection now tolerates sparse launch environments without `HOME`: home-scoped logs and DiagnosticReports are skipped with archive notes while package-local logs are still inspected, and staged/archive validation now requires those guards in `collect_macos_support_info.sh`.
- [x] Experimental macOS Apple-host guest scripts now resolve `~` through a validated `GUEST_HOME` from `HOME` or `OPENQ4_GUEST_HOME`, the host workflow exposes that override as `-MacHome /Users/<name>`, child tools receive the resolved `HOME`, malformed/nonexistent guest-home paths are rejected, wrong-host Darwin errors stay first, and Meson shims, workspace defaults, and Desktop launcher creation use the resolved home.
- [x] Experimental macOS signoff archive validation now caps tar member path and path-segment lengths before report/log parsing, so malformed copy-back archives with oversized path metadata fail with clear validation errors instead of producing unwieldy evidence diagnostics.
- [x] Experimental macOS package validation now rejects stale root engine binaries before publication: local `.install` validation, package roots, unsigned/runtime archives, manual release staging, and Apple-host signoff staging fail if extra `openQ4-client_*` or `openQ4-ded_*` files for another architecture are present, and manual release staging now also rejects extra `.dylib` game modules instead of only wrong-platform `.dll`/`.so` modules.
- [x] Experimental macOS staged-package validation now treats the support collector as a required runtime file: Darwin Meson installs stage `collect_macos_support_info.sh` beside the app and loose binaries, local staged validation checks that it is executable, UTF-8/LF-only, size-bounded, still contains the privacy/no-launch markers and bounded truncation fallback, and does not contain known environment-dump, launch-redirection, or xattr-value dump patterns; signed/notarized DMG source-tree preflight and macOS runtime archive validation reject placeholder, malformed, or privacy-unsafe collectors; and the Apple-host/manual-release workflows check the same support-collector markers before accepting a package.
- [x] Experimental macOS Apple GL 2.1 first-frame hardening now covers the July 5 issue #73 follow-up report for 0.6.91: after the degraded `ARB2 interaction driver bypass` path runs, openQ4 restores client-array, texture-unit, ARB program, stencil, and state-cache baseline before later classic frame passes, then records one-shot post-bypass breadcrumbs (`ARB2 interaction bypass state restored`, `ARB2 interaction bypass light scale`, `ARB2 interaction bypass ambient rescue`, and `ARB2 interaction bypass frame tail`) so any remaining crash identifies the next operation reached.
- [x] Experimental macOS Apple GL 2.1 first-frame hardening now covers the July 6 issue #73 follow-up report for 0.6.92, where the bypass reached `ARB2 interaction bypass light scale` before `SIGSEGV`: the degraded path now forces neutral `backEnd.lightScale`/`overBright`, skips the post-interaction `RB_STD_LightScale()` fullscreen pass entirely, and records `ARB2 interaction bypass light scale skipped` before continuing to the ambient rescue and frame-tail breadcrumbs.
- [x] Experimental macOS app-bundle detection now recognizes renamed `.app` bundles that still launch from a standard `*.app/Contents/MacOS` layout, so a renamed Finder bundle still reaches the adjacent package-root startup diagnostic instead of falling through to generic base-path probing. Release packaging remains pinned to `openQ4.app`.
- [x] Experimental macOS `openQ4.app` startup preflight now expects SP/MP game modules under the packaged `baseoq4/` directory instead of incorrectly requiring `q4base/game-sp_<arch>.dylib` and `q4base/game-mp_<arch>.dylib`; misplaced `q4base/game-*.dylib` copies are reported as mismatched runtime payload, so users no longer need the manual copy workaround before reaching the later runtime crash path.
- [x] Experimental macOS support collection is safer for public issue reports: generated support archives now use private permissions, are written through a temporary tarball before rename, refuse to overwrite an existing archive target, and bound copied package/log text plus DiagnosticReports text without falling back to full-file copies if truncation fails, so unusually large local files cannot create oversized support bundles.
- [x] Experimental macOS support collection now creates its output tarball through an output-directory `mktemp` path and rejects package-root or output-directory paths containing control characters before collection starts, closing a predictable-temp-name edge and preventing malformed archive paths in user reports.
- [x] Experimental macOS support collection now publishes the final support archive with a same-directory hard link from the private temporary tarball, so the collector fails instead of replacing a target that appears during collection.
- [x] Experimental macOS support collection now self-validates the generated support tarball before publishing it: the collector rejects empty or oversized compressed archives and runs a `tar -tzf` listing check before exposing the final support file to users.
- [x] Experimental macOS support collection now bounds direct diagnostic command output too: `sw_vers`, `system_profiler`, `xcodebuild`, `xcrun`, and similar command captures are stream-limited before redaction, preventing unusually noisy tools from inflating public support archives or temporary collector files.
- [x] Experimental macOS support collection now bounds and sanitizes generated report sections as well: package listings, path-resolution summaries, binary architecture/dependency reports, renderer/OpenAL summaries, signing checks, and quarantine reports are stream-limited before redaction, and copied public text replaces embedded NUL/control characters before entering the support archive.
- [x] Experimental macOS Apple-host workflow inputs now reject asset basepaths inside reserved workflow workspace children (`openQ4/`, `openQ4-game/`, `incoming-quake4/`, and `results/`) on both the host preflight and guest scripts, preventing `rsync --delete` asset installation from overwriting synced source trees, scratch extraction data, or collected signoff evidence.
- [x] Experimental macOS direct guest scripts now reject control characters in `OPENQ4_GUEST_HOME`/`HOME`, expanded workspace and asset-root paths, and the incoming Quake 4 asset tar path before creating directories, logs, launchers, or extracting archives; the asset installer also keeps its case-insensitive reserved-path guard import-complete for real execution, not only syntax checks.
- [x] Experimental macOS host workflow remote archive expansion and result collection now reject a control-character remote `HOME` fallback before expanding `~` or creating copy-back archives, so omitted `-MacHome` runs follow the same path hygiene as explicit guest-home overrides.
- [x] Experimental macOS host workflow reserved-directory preflight now compares `-MacWorkspace` and `-MacBasePath` through the `-MacHome`-resolved `~/` form with normalized case-insensitive keys, and source sync trims trailing workspace slashes before appending `openQ4/` or `openQ4-game/`, preventing mixed `~/...`/`/Users/...` inputs or trailing slash workspaces from slipping past host checks or producing double-slash extraction targets.
- [x] Experimental macOS signoff result copy-back now downloads remote archives into a same-directory temporary file, rejects empty transfers, rechecks the final path, and publishes without overwriting an existing archive for the same run ID, reducing the chance of stale or half-written local evidence.
- [x] Experimental macOS Apple-host result archiving now writes the remote copy-back tarball to a same-directory temporary file, rejects empty or unlistable tar output, and hard-links the validated archive into the final remote name without replacing an existing target before the host downloads it.
- [x] Experimental macOS guest signoff/build result directories now fail closed if the selected `<run-id>-<action>-<bridge>` directory already contains files before logging starts, so reruns cannot silently blend stale renderer evidence, logs, or reports into a new archive; operators should use a fresh `-MacOSRunId` or remove the old directory.
- [x] Experimental macOS archive and evidence intake now rejects more mismatched handoffs: package archive validation opens `.tar.gz` and `.tar.xz` artifacts with exact compression modes instead of auto-detecting mislabeled tarballs, and host/guest/signoff tooling caps result run IDs and action tokens before they become long paths, archive names, or evidence records.
- [x] Experimental macOS zip package validation now reports corrupt member payloads as clean package-validation errors, so a damaged `.zip` that opens successfully but fails while reading `Info.plist`, manifests, localized strings, or support metadata no longer escapes as a low-level Python `BadZipFile` traceback.
- [x] Experimental macOS package archive output and permission validation tightened again: release archives and DMGs refuse output paths inside the package tree they are archiving, zip runtime archive validation now checks every member's unsafe mode bits instead of only the final member, and dSYM symbol archive validation opens `.tar.xz` artifacts as xz-compressed tar data so mislabeled debug archives fail before publication.
- [x] Experimental macOS signoff result collection now preflights multiplayer smoke evidence on the Apple host before creating the copy-back archive, so incomplete `renderer-mp-smoke` output is caught before a result bundle reaches the local validator.
- [x] Experimental macOS signoff evidence recording now validates package artifact names before updating the evidence index: recorded artifacts must be safe exact-variant filenames for `openQ4` macOS arm64 `opengl`/`metal` packages, must avoid case-insensitive duplicates, must include artifacts for every requested graphics bridge, and must not include unrequested bridge artifacts so incomplete, stale, typoed, or path-like artifact entries cannot become release evidence.
- [x] Experimental macOS signoff evidence recording now rejects symlinked archive inputs before validation and symlinked evidence-index inputs before update, so completed Apple-hardware signoff records cannot be accepted or written through a symlinked handoff.
- [x] Experimental macOS signoff evidence recording now rejects control characters in free-form release metadata and in validated signoff report/log text, and reports malformed signoff tarballs as clean recorder errors, preventing broken Markdown records or Python tracebacks during release evidence intake.
- [x] Experimental macOS artifact identity checks are stricter: runtime package validation now requires the embedded `SYMBOLS.txt` `runtime_archive=` field to match the archive being validated, symbol manifests reject path-like runtime or symbol archive names, and completed signoff evidence archives must keep the canonical `openq4-macos-results-<run-id>.tar.gz` filename so renamed or stale copy-back bundles cannot pass as the current Apple-hardware run.
- [x] Experimental macOS `SYMBOLS.txt` validation is stricter: dSYM symbol archives now parse manifest headers and binary rows, reject duplicate or unknown manifest keys, require the runtime archive name, symbol archive name, and package suffix to match the artifact being validated, and verify the expected app/client/dedicated/game-module entries with valid SHA-256s, positive sizes, matching Mach-O architecture UUID lines, and exact dSYM mappings so mixed OpenGL/Metal, renamed, stale-architecture, or incomplete symbol bundles fail before publication.
- [x] Experimental macOS archive validation now caps total expanded payload size as well as individual entries: runtime packages, dSYM symbol archives, and collected signoff evidence archives reject archives whose declared uncompressed contents exceed conservative publication/evidence limits before metadata or evidence files are read.
- [x] Experimental macOS symbol packaging now fails closed on unsafe staging/output paths: stale symbol staging roots must be real directories before cleanup, dSYM outputs reject stale symlinks or files instead of unlinking through them, and symbol tarballs refuse symlinked input roots or archive outputs placed inside the symbol staging tree.
- [x] Experimental macOS archive validation now has explicit resource and permission guardrails: runtime package archives and dSYM symbol archives reject excessive member counts, symbol archives reject Finder/AppleDouble sidecars and apply the bounded metadata-size check before reading `SYMBOLS.txt`, and collected macOS signoff evidence archives reject special-mode or group/world-writable members before evidence is accepted.
- [x] Experimental macOS workflow archive intake is stricter before Apple-host sync or asset installation: remote source/GameLibs transfer archives and Quake 4 asset tarballs now get structured `tarfile` member preflight before extraction, reject duplicate members, case-insensitive member collisions, macOS metadata/debug sidecars, malformed/control-character paths, symlinks, hardlinks, and special files, and use `COPYFILE_DISABLE=1` on remote tar listing/extraction paths to avoid materializing AppleDouble sidecars during workflow transfers.
- [x] Experimental macOS robustness improved again: app-bundle startup failures now report the expected runtime architecture, per-file reasons such as missing/non-executable entries, and any obvious stale x64/x86 or wrong-platform game-module files found beside the app; the support collector no longer depends on GNU-style `dirname --`, suppresses AppleDouble sidecars when creating support archives, records the detected runtime architecture token in package path reports, rejects symlinked/non-directory package roots, refuses unsafe support archive output targets, no longer follows symlinked package/log/crash-report evidence inputs, and skips DiagnosticReports names that are not archive-safe; macOS package archive validation now rejects missing/symlinked inputs, unknown archive formats, and control-character member names in tar/zip artifacts, macOS dSYM symbol archives reject symlinked inputs plus duplicate or case-conflicting members before publication, and the macOS asset installer preflights tar members with structured metadata before extraction; `macos-static` now syntax-checks the Apple-host guest bootstrap, asset-install, and build/signoff scripts; and the host/guest Apple workflow keeps Meson build output constrained to `builddir*` or `.tmp/` paths.
- [x] Experimental macOS support archives now collect richer package/startup diagnostics without launching the game: `system/rosetta.txt` records collector-process architecture plus `sysctl.proc_translated` state for unsupported Rosetta reports, `logs/renderer-summary.txt` preserves renderer startup, driver-quirk, ARB2, and fatal-signal breadcrumbs from existing logs, `package/binary-architecture.txt` captures read-only `file`/`lipo` architecture output, `package/dylib-dependencies.txt` captures `otool` dependency and game-module install-name output, `package/signing.txt` captures read-only `codesign`, `spctl --assess --type execute --verbose=4`, and `xcrun stapler validate` output for the app, loose binaries, and game modules, and `package/quarantine.txt` reports extended-attribute names plus `com.apple.quarantine` presence without copying xattr values. The collector also inspects any loose `x64` or `x86` executable names that are present so accidental extra-architecture package contents are visible without expanding the supported release matrix.
- [x] Experimental macOS crash intake now includes dedicated-server reports too: the issue template and support-data guide distinguish `openQ4.app`, loose client, and loose `openQ4-ded_arm64` launches, while the support collector copies recent `openQ4-ded*.ips`/`.crash` DiagnosticReports alongside app/client crash reports.
- [x] Apple OpenGL 2.1 compatibility now bypasses ARB2 light interactions on the fragile M4/M5 startup path reported in GitHub issue #73 comment 4874832470; the fallback records `ARB2 interaction driver bypass`, keeps the CPU-backed vertex cache and `SimpleInteraction.vfp` shader selection, and applies the ambient rescue floor so affected experimental macOS launches degrade visually instead of walking into the first-frame `SIGSEGV`.
- [x] OpenAL audio has been restored to the end-of-day June 23 sound implementation; later speaker-detection, voice, streaming, diagnostic, and source-lifecycle experiments were removed from the shipped audio runtime.
- [x] OpenAL playback is steadier in dense or long-running audio scenes: queued streams now refill and drain their available buffers, trim stream chunks to each sample's playable range, convert sound times to sample offsets with bounded 64-bit math, align restart offsets to the retail/OpenAL block boundary, keep leadin-to-loop handoff inside playable ranges, restore retail-style mixer ranking based on audible volume plus protected voice/dialogue channels, skip inaudible channels before they can compete for dense-scene voice slots, restore retail diversity mapping for sample choice and deterministic loop starts, apply looping random-start diversity to leadin-then-loop sounds too, make `no_dups` sample selection see the outgoing channel before channel replacement, release stopped channels and their hardware voices immediately, accumulate and save/restore retail-style shifted playback time for live `frequencyShift` changes so resumed voices, completion, leadin volume, and shake/amplitude sampling stay on the same clock, remove the non-retail one-shot/loop/music priority boosts that could reorder busy combat scenes, stop evicting existing emitter channels during sound admission, preserve a real extra mixer candidate while keeping the full configured voice budget, apply music volume/mute handling to the retail musical sound class as well as flagged music samples, reset reused OpenAL sources to a neutral silent mix state before new playback, release failed starts or backend queue/update failures immediately, keep paused or queued voices busy until playable buffers are drained, and fail closed on unplayable payloads instead of holding silent sources.
- [x] The macOS no-runtime validation track is now a named local profile: `macos-static` runs Python static/policy tests, synthetic Apple GL 2.1 coverage, synthetic macOS package/archive fixtures, shell syntax checks, and push/PR validation dry-runs without building or running openQ4 on macOS. Windows/Linux maintainers can invoke it through `validate_macos_static.ps1` or `validate_macos_static.sh`, and optional renderer self-test binaries are guarded so they run only on available non-macOS hosts.
- [x] Experimental macOS GameLibs alignment is now enforced across both repositories: `openQ4` keeps SDK/game-library Darwin and Clang support in the companion `openQ4-game` repo instead of a copied `src/game` mirror, static checks guard arm64 `.dylib` names and `@loader_path` install names, macOS build logs report concrete architecture strings such as `macos-arm64`, package/support metadata records both openQ4 and `openQ4-game` commits, and `openQ4-game` CI now checks ARM64-sensitive allocation, savegame serialization, pointer-width, and alignment-sensitive allocation assumptions.
- [x] Experimental macOS audio-provider policy is now explicit: release builds stay pinned to the Apple OpenAL framework, `-Dmacos_openal_provider=system` is documented as local OpenAL Soft migration-only coverage, future OpenAL Soft packaging must define library location/install names/codesigning/license/notarization allowlists first, and macOS crash/support intake now asks for OpenAL vendor, renderer, device, and EFX lines with `logs/openal-summary.txt` collected from existing logs.
- [x] Experimental macOS package robustness is clearer without requiring Finder testing: app-bundle launches now fail with a localized macOS startup error when `openQ4.app` is separated from its adjacent package root, generated bundles carry localized `OpenQ4PackageRoot.strings` resources, package validation requires those resources, and the support collector writes `package/path-resolution.txt` with the package root, app path, expected loose runtime paths, and any copied `fs_basepath`/`fs_cdpath`/`fs_savepath` log lines.
- [x] Experimental macOS app-bundle detection now recognizes renamed `.app` bundles that still launch from a standard `*.app/Contents/MacOS` layout, so a renamed Finder bundle still reaches the adjacent package-root startup diagnostic instead of falling through to generic base-path probing. Release packaging remains pinned to `openQ4.app`.
- [x] Experimental macOS app-bundle package-root checks now reject symlinked adjacent runtime entries at launch, so hand-assembled or damaged packages fail with a clear `symlink` diagnostic instead of silently following `baseoq4/`, client, dedicated, or game-module symlinks.
- [x] Experimental macOS releases now publish symbolication data without putting debug payloads in runtime packages: the macOS packager writes `SYMBOLS.txt` with binary SHA-256s, Mach-O UUIDs, and the matching dSYM archive name, creates separate `openq4-<version>-macos-arm64-<bridge>-symbols.tar.xz` artifacts for the app executable, loose client/dedicated binaries, and SP/MP game dylibs, and the manual release workflow verifies/uploads those symbol archives beside the runtime DMG or unsigned tarball.
- [x] Experimental macOS Apple GL 2.1/ARB2 startup hardening is stricter for issue #73-style reports: renderer driver quirks now match Apple OpenGL 2.1 using normalized vendor/version and selected context facts, synthetic M4/M5/macOS 15/macOS 16/Tahoe/unknown-renderer cases force CPU-backed vertex cache plus `SimpleInteraction.vfp`, bypass ARB2 light interaction draws on the unstable compatibility path, quirk logs show VBO/PBO/simple-interaction/ARB2 impacts, and the fallback now fails closed with `Unsupported Apple OpenGL 2.1 compatibility path` if the required simple interaction shader pair cannot load.
- [x] Experimental macOS renderer crash diagnostics now record the last renderer startup phase for issue #73-style failures: normal logs show ordered breadcrumbs through `R_InitOpenGL`, ARB2 reload, upload setup, backend selection, `first ARB2 interaction handoff`, and the Apple GL 2.1 `ARB2 interaction driver bypass`; POSIX fatal-signal output prints `last renderer startup phase`; and static validation keeps those crash breadcrumbs, docs, and release notes aligned without requiring macOS runtime testing.
- [x] Experimental macOS crash intake is now more actionable for issue #73-style reports: GitHub has a dedicated macOS crash template, packages include a safe `collect_macos_support_info.sh` collector, the user guide explains which terminal/log/crash artifacts to attach, macOS release notes point to that guide, and static validation keeps the template, docs, packaging contract, and no-macOS-runtime boundary aligned.
- [x] Experimental macOS support-claim guardrails now cover curated release notes and front-door package docs: the README, packaged HTML README, platform support page, and 0.6.5 curated notes all say Apple Silicon/arm64 where current macOS packages are described; the release-completion checklist now blocks first-class/stable wording without `docs/dev/macos-signoff-evidence.md`; and GitHub issue #73 stays in the macOS release-note limitation boilerplate until it is closed or explicitly carried forward with matching evidence.
- [x] Experimental macOS renderer/backend support now has an explicit policy: `docs/dev/macos-renderer-backend-policy.md` records the SDL3/OpenGL release path, keeps the `-metal` package labeled as a Metal bridge rather than native Metal, classifies the legacy native Cocoa/OpenGL backend as comparison-only diagnostic infrastructure, and lists the design gates required before any native Metal or native-backend release claim can be made. Meson now warns when macOS `platform_backend=native` is selected so local comparison builds do not look like release support coverage.
- [x] Experimental macOS native backend containment now has a dedicated policy and static guard: `docs/dev/macos-native-backend-containment-policy.md` keeps Carbon and NSOpenGL isolated to the native comparison backend, keeps SDL3 Darwin sources and SDL3 framework linkage Carbon-free, keeps native Cocoa/OpenGL support wording out of curated release notes, records that the native backend should get a removal plan after SDL3 coverage is mature instead of being kept indefinitely by default, and keeps native Metal implementation work blocked until a separate renderer design plan exists.
- [x] Experimental macOS support now has an explicit matrix policy: `docs/dev/macos-support-matrix-policy.md` records the current Apple Silicon/arm64-only release scope, keeps Intel Mac, universal2, and Rosetta outside published support, and requires signoff evidence for both the macOS 11 floor and the latest public macOS before any first-class macOS claim can ship. macOS signoff reports and evidence records now include architecture policy, CPU architecture, OS matrix role, Xcode version, and macOS SDK version so release notes can match the actual Apple toolchain and OS coverage.
- [x] Experimental macOS package UX is now release-gated: the macOS Package Layout And Release Policy in `docs/dev/macos-package-layout-and-release-policy.md` records the supported adjacent package-root layout, marks moving only `openQ4.app` unsupported until a self-contained bundle migration exists, and requires mounted-DMG, copied-package, terminal, path-resolution, and Gatekeeper signoff evidence before macOS support claims can grow beyond the current experimental tier. The manual release workflow now has an explicit first-class macOS support tier that fails instead of publishing unsigned fallback packages when signing/notarization credentials are missing.
- [x] Experimental macOS runtime signoff is now evidence-gated: `docs/dev/macos-signoff-evidence.md` records the active run ID, archive checksum, hardware/OS details, OpenGL/Metal bridge coverage, OpenAL provider, package artifacts, openQ4 and `openQ4-game` commits, SP/MP evidence, and release-note limitations; `tools/macos/record_signoff_evidence.py` turns a completed-checklist archive into that record, while collected signoff archives now require a dedicated `renderer-mp-smoke` evidence folder before validation accepts the hardware signoff bundle.
- [x] Multiplayer server entry is now more deliberate and Quake Live-like: new clients start as spectators at the map intermission camera when available, see a Q4-styled join screen with server details and gametype-aware join options, and can set `ui_autoJoin 1` to keep the old immediate-join behavior.
- [x] Pull-down console mouse selection and clicks now line up with the visible cursor hotspot, so the cursor no longer appears a row below the point that receives input.
- [x] Documentation now labels macOS support as experimental wherever current support or release packages are described, including the README, getting-started guide, build/platform docs, input guidance, packaged HTML docs, VM workflow, and curated release notes.
- [x] Experimental macOS packaging and signoff checks now reject case-insensitive path collisions, Finder/AppleDouble metadata inside result archives, and renderer evidence that contains only placeholder directories before artifacts are accepted.
- [x] Experimental macOS VM asset installation and package validation fail closed on more host filesystem hazards: Quake 4 asset archives and installed asset trees now reject macOS metadata/debug artifacts plus case-insensitive path collisions, and macOS ZIP packages reject directory entries even when external mode bits are missing.
- [x] Experimental macOS VM automation now validates host SSH targets, user names, identity files, transfer roots, transfer archive outputs, result directories, source archive roots, and guest workspace/asset paths before syncing or running `rsync --delete`, and refuses Finder/AppleDouble/debug sidecars before creating source or asset transfer archives.
- [x] Experimental macOS VM workspace and asset roots now must be absolute POSIX paths or `~/` paths: the host rejects relative, dot-segment, empty-segment, and backslash inputs, and the guest scripts recheck the expanded paths before source sync, asset install, build, smoke, signoff, or result collection work.
- [x] Experimental macOS VM sync and result collection now revalidate remote archive files, extraction targets, single-root transfer archives, result workspaces, and result archive output paths on the Mac side before unpacking or copying evidence back to the host.
- [x] Experimental macOS release packaging now enforces an explicit `openQ4.app` bundle allowlist, rejecting unexpected nested app files such as stale helpers, frameworks, or debug payloads both in the package directory and in generated archives.
- [x] Experimental macOS app bundle validation now also enforces the expected bundle directory layout, so stale empty directories such as unused `Contents/Frameworks/` folders fail before signing, notarization, or archive publication.
- [x] Experimental macOS signed app bundles now treat `Contents/_CodeSignature/CodeResources` as a first-class, tightly validated bundle manifest, allowing legitimate ad-hoc/Developer ID signatures while still rejecting unexpected signature-side payloads.
- [x] Experimental macOS DMG creation and notarization upload now re-preflight the source tree at the final image boundary, catching symlinks, special files, unsafe modes, Finder/AppleDouble/debug metadata, case-insensitive collisions, and quarantine attributes before `hdiutil` or notary archive creation.
- [x] Experimental macOS archive output handling now rejects stale symlink, directory, non-regular, or symlink-parent paths consistently for release archives, direct DMG creation, and temporary notarization ZIPs before external Apple packaging tools run.
- [x] Experimental macOS signoff result collection now preflights result directories before creating the host-return archive, rejecting symlinks, special files, Finder/AppleDouble metadata, case-insensitive path collisions, empty report/log files, and renderer evidence folders with no files while suppressing macOS copyfile sidecars during tar creation.
- [x] Experimental macOS VM scripts now reject symlinked asset archives and guest helper scripts, suppress macOS copyfile sidecars while extracting asset tarballs, validate host copy-back targets before `scp`, and publish Desktop launchers plus signoff reports through temporary files instead of writing final paths directly.
- [x] Experimental macOS signoff archive validation now rejects symlinked archive inputs, control characters in tar member names or required UTF-8 report/log contents, excessive member counts, reports that do not record a staged client path, and reports/logs that do not reference the expected result and renderer evidence directories.
- [x] Experimental macOS guest signoff runs now validate result directories before opening workflow logs, refuse symlinked staged client launch targets, and recheck existing Quake 4 asset basepaths for symlinks, special files, Finder/AppleDouble metadata, and case-insensitive path collisions before gameplay smoke tests.
- [x] Experimental macOS guest scripts now refuse stale symlink, directory, or special-file workflow log, launcher, and signoff report paths before writing, so repeated Apple-host signoff runs cannot accidentally append evidence to the wrong filesystem target.
- [x] Fullscreen startup is smoother on the shared SDL3 backend: when `r_fullscreen 1` is saved, the render window now stays hidden until its configured fullscreen state is applied, so launch transitions from the splash screen to fullscreen without flashing a windowed game window.
- [x] macOS validation is stricter before signoff evidence is accepted: staged Mac payload checks now reject stale or wrong-platform game modules, missing icon/splash support files, symlinks, and special files, while collected signoff archives must include binary-architecture evidence plus macOS display, audio, USB, and Bluetooth inventory.
- [x] Multiplayer bright-skin character materials now preserve their authored glow overlays, so models such as Pro Marine keep the intended head color instead of receiving a flattened, desaturated overlay from the modern material path.
- [x] Cinematic framing now preserves retail Quake 4 composition across display shapes: top, bottom, left, and right black bars update with the viewport so the visible cinematic area stays 16:9 through letterboxing or pillarboxing as needed.
- [x] The temporary OpenAL voice experiment and its compatibility controls were removed; the live audio code now matches the June 23 baseline directly instead of carrying a runtime switch.
- [x] Multiplayer players can opt into clearer enemy and teammate readability: client-side outline and rimlight cvars add colored visibility effects to player bodies, heads, and world weapons without changing server snapshots, hit detection, or stock asset requirements.
- [x] Multiplayer appearance settings are easier to tune from the in-game menu: the Settings page now separates Self, Enemy, and Teammate tabs, uses dropdown model selection, exposes force-model, outline, rimlight, and brightskin controls, and previews the selected model with the active visibility colors.
- [x] Multiplayer score displays are refreshed from current rank data when the full scoreboard or mini score HUD draws, and unknown ammo snapshot values no longer spam `idBitMsg::WriteBits` overflow warnings during listen-server play.
- [x] Listen-server multiplayer clients now receive short-lived hit, impact, and explosion presentation events more reliably when PVS data is not available at the exact emit frame, and first-person view-weapon visuals update on the final client prediction step to reduce loopback weapon jitter.
- [x] Content-only mods are easier to run: when a selected mod does not ship its own SP/MP game module, openQ4 now falls back to the matching `baseoq4` module instead of requiring copied dynamic libraries in every mod folder.
- [x] Release packages now avoid debug-build performance costs on every platform: Windows release jobs moved from Meson `debug` to the optimized diagnostics profile already used by Linux and experimental macOS, release builds disable runtime asserts with `b_ndebug=true`, and symbol artifacts remain available through Windows PDBs plus Linux debug-symbol archives.
- [x] Follow-up Strogg flyer/fighter destruction fix: map-authored `func_fx` explosions now service newly submitted BSE effects immediately like retail Quake 4, so one-shot explosion, smoke, sound, and debris bursts are no longer skipped before their first visible frame.
- [x] Single-player tram combat is more stable in debug and local validation builds: long straight AI projectile aim probes are split into collision-safe segments, preventing the `game/tram1b` tram-rail fight from tripping the debug `Clip.cpp` huge-translation assertion while keeping the collision guard active for invalid oversized sweeps.
- [x] Validation and release tooling now rejects more unsafe source inputs before build/package work starts: source roots, GameLibs roots, Meson-discovered source files, Windows runtime staging paths, OpenAL overrides, staged GameLibs manifests, macOS signing files, and documentation links/sources fail closed on symlinks, missing files, or root escapes; legacy Linux shell helpers also pass broad `bash -n` syntax checks.
- [x] Build, release, and legacy PK4 tooling now fail closed on more malformed inputs: legacy PK4 builders, installer generation, staged-content pruning, release-doc generation, and default GameLibs discovery reject symlinked roots before resolving paths; release archives strip host ownership/mtime metadata; package text manifests replace output symlinks with LF-normalized files; packaged asset changes are no longer treated as docs-only; Discord release announcements validate webhook/GitHub URLs; legacy Linux PK4 helpers run under Python 3 and clean up temporary update work safely.
- [x] Save-game handling is more robust across platforms: save, load, and delete now share the same scrubbed slot filenames, malformed empty names fail before touching disk, failed description sidecar writes clean up partial saves, and SP/MP GameLib bounds/angles serialization uses endian-aware float/vector writes.
- [x] Starting a new single-player game or loading a save after a game-module switch now resumes the requested map/load action after reload instead of falling back to the main menu, and reload logs continue across the module restart for support diagnostics.
- [x] Packaged single-player loading screens are more complete: Core 1, Core 2, Network 1, Network 2, Process 1, Process 2, Storage 1, and Tram 1 now include the missing DDS/TGA loadscreen image sets in `baseoq4/pak1`.
- [x] Packaged map loading screens now include the missing source loadscreen files found in the local `.home` runtime tree, including camera sidecars plus the MCC 2, Putra, Recomp, and Waste DDS/TGA image sets, so packaged builds no longer depend on local-only loadscreen captures.
- [x] Packaged single-player light-grid coverage is more complete: Air Defense 1, Core 2, and Waste now include the missing baked light-grid source assets in `baseoq4/pak1`, avoiding local `.home`-only coverage gaps.
- [x] OpenAL has been restored to the end-of-day June 23 sound implementation for default reliability, and the disabled revalidation slice has been removed from the shipped runtime.
- [x] Multiplayer client joins are more reliable after map load: valid compressed in-band net messages are no longer rejected as truncated when the run-length decoder reports its final bitstream cursor past the logical output size, so loopback clients can stay connected through MP validation while malformed short decodes still fail closed.
- [x] Release and staging automation avoids another class of symlink and malformed-output mistakes: generated changelogs refuse symlinked outputs and symlinked curated notes, Windows installers reject symlinked payload/template/output paths, stale staged-content cleanup unlinks known stale symlinks without following their targets, validation rejects symlinked build directories, generated headers/manifests replace destination symlinks safely, legacy PK4 staging uses the shared safe copy helper, and GameLibs staging rejects raw symlink stage roots before resolving them.
- [x] Build and release tooling now closes more raw-path gaps before packaging or validation work begins: shared copy helpers reject symlinked sources, icon sync refuses symlinked inputs and generated outputs, PK4 builders/checksum headers validate raw source/manifest/archive paths, package and fast-stage roots fail closed on symlinks, source-listing helpers reject symlinked roots, archive outputs report invalid directories clearly, and generated documentation link checks use isolated temp workspaces for concurrent runs.
- [x] Validation runs now fail earlier on unsafe inputs and malformed staged packages: the push/PR runner rejects invalid job counts, non-positive runtime timeouts, bad source roots, unsafe build directories, staged symlinks, nested linker artifacts, mismatched runtime architectures, wrong-platform game modules, and stale or missing Windows PDBs before a package can be trusted.
- [x] Release automation is harder to mis-publish: generated changelogs now honor their selected source checkout, validate release metadata before turning it into GitHub links, escape untrusted Markdown text, choose the correct prior tag, avoid nightly iteration collisions, reject release-version downgrades, guard packaged docs output paths, and verify Windows installers include the runtime DLLs they actually need.
- [x] Release packaging now rejects more malformed or unsafe inputs before archives are produced: generated docs refuse symlinked outputs, symlinked Markdown sources, symlinked copied assets, and unsafe URI schemes; changelog workflow URLs are checked for Markdown-breaking characters; package versions and package roots are validated before replacement; Windows runtime staging rejects symlinked binaries/content; staged game-library manifests require the expected schema; and CI smoke tests track the central validation list.
- [x] Local validation and release packaging guard more edge cases before publication: the push/PR Python suite now includes the platform, renderer, and settings tests that were previously CI-only; Windows runtime staging fails on malformed or mixed-architecture payloads and missing OpenAL runtimes; fast install staging rejects build/install directory overlap; installer script generation validates file-safe release tokens and escaped paths; macOS archives reject malformed path segments; and Discord release announcements include signed DMG plus unsigned macOS archive links.
- [x] The graphics settings menu no longer advertises renderer backends that openQ4 does not ship: the old Renderer Backend picker is now an advanced Renderer Fallback control with only Auto and explicit ARB2 rollback choices, while legacy config values continue to fall back safely through the engine warning path.
- [x] The System settings post-effects rows no longer overlap in compact display layouts: Renderer Fallback, irradiance, bloom, SSAO, HDR Tonemap, and CRT now keep separate rows so each setting can be read and changed from the menu.
- [x] Build and release packaging safety is tighter: PK4 source paths, accepted archive entries, fast `.install` staging roots, package version tags, copied package inputs, and staged GameLibs manifests now fail closed on path escapes, symlinks, duplicate metadata, or non-regular files, with regression coverage in the local validation suite.
- [x] Linux ARM64 save-game loading no longer crashes while restoring fresh saves: SP/MP game objects now keep 16-byte aligned allocation headers when savegame restore recreates entities, avoiding ARM64-only misaligned object pointers.
- [x] Linux ARM64 save-game restore is more reliable after the allocation fix: SP/MP script objects now read their saved payload sizes through the 32-bit save format instead of aliasing into pointer-width storage, preventing valid saves from hanging or failing during script-object restore on ARM64 hosts.
- [x] Save-game writes are now staged through temporary files and promoted with backups after header validation, while malformed save headers, persistent player info, minigame restores, and SP/MP game-object restore counts use domain-specific caps with SP/MP parity checks so corrupt files fail closed before overwriting good slots or driving runaway allocations. Arcade minigame integer payloads now use versioned little-endian save sections with legacy raw-format fallback, and SSD minigame restores validate level, weapon, pool, and entity-reference counts before indexing fixed arrays.
- [x] Save-game restore now fails closed more consistently in multiplayer game modules too: MP object creation, raw reads, strings, windings, dictionaries, and event payloads use the same bounds and short-read checks as the single-player restore path.
- [x] DXT5/RXGB normal-map DDS replacements now decode through the normal-map path when loaded as bump maps, so mod texture packs can replace `.tga`/`.jpg` bump sources with RXGB-style DDS files without losing the X/alpha normal channel.
- [x] Texture replacements can now drop a valid `.dds` beside an existing `.tga` or `.jpg` reference and have openQ4 prefer the DDS source automatically: supported DXT and BC7 mip chains upload directly when the renderer exposes the required compression format, with a decoded DXT fallback on older renderers.
- [x] Explicit BC7 DDS textures can now load directly as precompressed BPTC images on OpenGL 4.2/BPTC-capable renderers, including standard DX10 BC7 headers and common BC7 FourCC variants; unsupported drivers fail closed with a clear warning instead of mis-decoding the image.
- [x] BC7 texture handling is more robust and now matches dhewm3's replacement workflow end to end: `image_usePrecompressedTextures 2` follows the BC7-only policy, canonical `dds/` names are derived from complete image programs before extension-normalized and same-folder compatibility paths, header-only probes validate complete declared mip layouts without rereading full texture payloads, direct DXT/BC7 uploads honor downsize and unmipped policies, stale or malformed automatic replacements fall back to the original source, source-selection changes are detected by `reloadImages`, BC7 bump maps preserve authored alpha/RXGB data, `image_showPrecompressedTextures` reports exact selections, `imageDDSSelfTest` covers naming and malformed DX10 metadata, and `gfxInfo` reports DXT and BC7/BPTC availability.
- [x] Manual releases keep experimental macOS downloads visible when Apple signing credentials are absent: the workflow now always builds the experimental macOS ARM64 OpenGL and Metal packages, publishes signed/notarized DMGs when Developer ID/notary secrets are configured, and otherwise publishes clearly labeled `-unsigned.tar.gz` archives that are ad-hoc signed only for bundle validity and expected to show normal Gatekeeper warnings.
- [x] Manual experimental macOS ARM64 releases package successfully again: the app bundle executable and adjacent `openQ4-client_arm64` binary now keep separate valid macOS signatures, so OpenGL and Metal archives verify both launch paths without copying a bundle-scoped signature onto the standalone client.
- [x] Manual experimental macOS ARM64 release packages now keep SP/MP game module install names package-relative after Meson staging, so unsigned and signed OpenGL/Metal archives no longer point back at the CI workspace `.install` directory.
- [x] Manual releases no longer fail late when Apple signing credentials are absent: the workflow now builds a credential-aware release matrix, keeps Windows/Linux packages mandatory, includes signed/notarized experimental macOS DMGs only when all Apple Developer ID and notary secrets are configured, and verifies the expected artifact list against the matrix that actually ran.
- [x] Runtime PK4 packaging now tracks `content/baseoq4/pak0/` and `content/baseoq4/pak1/` through generated source manifests, so removing generated content such as obsolete Air Defense 1 light-grid chunks no longer leaves stale Ninja dependencies that break the fast local build.
- [x] Runtime logs now begin with the openQ4 product name and version before the log-open timestamp, making copied crash and support logs immediately identifiable.
- [x] Experimental macOS push verification builds are unblocked again: process handoff path and environment filtering now use openQ4's guarded string-compare API instead of raw C string calls rejected by the shared idStr safety wrappers.
- [x] Renderer helper and diagnostic loops do less repeated count work: portal export/find/show paths, MSAA resolves, light and subview winding projection, debug polygon drawing, material register printing, shadow-planner descriptor checks, and benchmark preset scans now cache stable loop counts before iterating.
- [x] Renderer geometry helper overhead is lower: GUI clipping, decal projection and decal winding emission, overlay creation, portal screen/fade checks, GUI/model tracing, and polytope surface construction now cache stable winding point and model surface counts while building or tracing geometry.
- [x] Renderer classic material passes do less repeated CPU work: soft particles, RV depth capture, depth fill, ambient shader passes, blend lights, lightgrid submission, shadow-map casters, point-shadow casters, custom GLSL interactions, and subview generation now cache material stage counts before scanning stages.
- [x] Renderer loading and legacy surface-scan overhead is lower: binary image uploads, lightgrid packed-image materialization, world-model export checks, decal projection, interaction creation, and GUI trace diagnostics now cache stable image/entity/surface counts before iterating.
- [x] Baked light-grid playback no longer streams atlas textures through the per-frame draw path: packed map grids are materialized during map setup and kept resident for the loaded level, removing the repeated pack reads, decode work, GL uploads, and purge/reload churn that caused traversal hitches.
- [x] Renderer CPU overhead is lower in frame preparation, diagnostics, and modern pass ownership: renderer loops now cache submit-command, graph-pass, packet-record, material-record, scene, and resource counts before iteration instead of repeatedly querying the same frame objects.
- [x] Renderer CPU overhead is lower in another command-submission pass: modern submit commands now cache material texture handles, texture-table indices, loaded semantic masks, and alpha-test metadata, while executor texture binding and material flag setup reuse those cached values and several legacy stage loops avoid repeated stage-count lookups.
- [x] Renderer CPU overhead is lower in material and clustered-light hot paths: material records cache semantic texture indices and loaded/color masks, modern draw and executor checks reuse those cached masks instead of scanning bindings, texture binding avoids redundant active-unit switches, and clustered light stage loops reuse cached stage counts.
- [x] Renderer CPU overhead is lower in another set of modern-path hot spots: render graph pass/resource lookups use cached masks and pointer fast paths, submit sorting avoids unnecessary range work, forward+ decal classification is cached per command, texture binds skip redundant active-unit changes, and material stage scans reuse cached stage counts.
- [x] Renderer CPU overhead is lower in modern-path preparation and submission: per-frame plan resets avoid clearing unused draw slots, modern draw planning reuses pass/shader decisions across draws, submit commands carry cached material and cull metadata, visibility bounds use cheaper clip transforms, and GL multi-bind calls now update only the changed binding range.
- [x] Core 2 AI navigation and combat recovery are more reliable on x64 builds: the AAS loader now matches retail Quake 4's tolerant AAS handling, initializes parsed area/reachability runtime fields deterministically, and lets AI resume normal combat after scripted idles while tactical route checks continue, preventing Makron arena enemies from standing idle instead of engaging.
- [x] World sound attenuation and volume now track retail Quake 4 more closely: master volume is applied in channel gain, sound fades use the retail linear scale, distance falloff honors `s_quadraticFalloff`, and radio/speaker world sounds receive the recovered Quake 4 attenuation pass.
- [x] OpenAL world-audio occlusion now sounds more physical: blocked portals and enviro-suit muffling are tracked separately, with stronger high-frequency loss on the direct path and a gentler filtered EFX send so walls, doors, and suit effects read as muffled instead of merely quieter.
- [x] Sound diagnostics and reload handling are closer to retail Quake 4: `listSounds`, `listSoundDecoders`, and `reloadSounds` are available again, `listSamples` remains as an OpenQ4 alias, and sound restarts now reload sample buffers after the OpenAL context is rebuilt.
- [x] Sound shader pitch behavior is closer to retail Quake 4: `s_frequencyShift` now gates frequency-shift playback, pitch shifts are clamped to the recovered retail range, live shifted playback time is accumulated and restored across pitch changes, delayed hardware starts advance by shifted playback time, non-looping shifted sounds use adjusted completion timing, amplitude/shake sampling follows the shifted playback position, and `s_skipStartSound` is available for retail-style sound start suppression diagnostics.
- [x] Voice and radio sounds now keep their mixer priority under the retail-style attenuation path, preventing dialogue and radio chatter from being stolen by lower-priority ambient channels when the sound world is busy.
- [x] Quake 4 sound parameter compatibility is restored between the engine and game modules: sound override parms now keep the retail `attenuatedVolume` layout field, Doppler and no-random-start flags use the retail bits again, and shader `frequencyshift` metadata no longer changes playback pitch by itself.
- [x] Skipping the Air Defense 1 intro cinematic no longer lets skipped intro voice or scripted audio resume after gameplay starts; cinematic skip now advances sound time with the fast-forwarded game frames so muted cinematic-only sounds age out during the skip.
- [x] Air Defense 1 cinematic effect sounds now clean up like retail: BSE reference sound emitters are freed once through the render-effect teardown path, skip-intro cleanup no longer double-frees/reuses the same emitter handle, late skip-shutdown string-pool cleanup no longer asserts or crashes, effect sound channels no longer collide with voice-only muting, and `listPlayingSounds` reports the actual non-zero-gain OpenAL sample files still active for debugging.
- [x] Native Wayland input validation now includes repeated mouse-capture stress: `sdl3MouseCaptureDiagnostics` accepts a bounded repeat count, CI runs a four-cycle relative mouse activate/deactivate case under Weston, and the log checks confirm capture state returns cleanly after repeated toggles.
- [x] Native Wayland window validation now has a stress case beside the basic lifecycle smoke: the Weston CI matrix repeatedly switches between windowed and fullscreen states with changing window sizes, keeping compositor-negotiated `vid_restart` behavior, pixel-size reporting, and fullscreen/windowed state logs covered separately from the shorter startup smoke.
- [x] Linux package metadata validation is harder to fool with malformed inputs: staged validation and release packaging share the same desktop-entry parser, reject duplicate `Exec=` keys, malformed shell quoting, NUL data, or field-code-only commands, and a deterministic fuzz smoke exercises those parser paths plus staged-source manifest hash/path invariants.
- [x] Linux packager guidance is clearer: `BUILDING.md` now names the SDL3 floor, Ubuntu 24.04-class userspace target, Wayland/X11/libdecor troubleshooting environment variables, `openQ4-game` staging manifest expectations, required validation commands, and the detached Linux debug-symbol archive that should stay paired with optimized release packages.
- [x] Linux sanitizer coverage now catches compile-time regressions earlier: PR validation has an x64 ASan+UBSan build lane for the engine plus staged SP/MP game modules, disables precompiled headers for clearer sanitizer diagnostics, preserves the openQ4-game staging manifest as a CI artifact, and documents the matching local validation command.
- [x] The companion openQ4-GameLibs repository now makes its Linux role explicit and testable: CI runs a source-input contract check, README/agent guidance states that Linux modules are compiled through openQ4's staged engine build rather than a standalone GameLibs target, and the Meson unsupported-platform guard is covered by static validation.
- [x] Linux builds and Wayland validation are harder to regress: Meson now applies stack/FORTIFY plus RELRO/NOW/no-exec-stack hardening to Linux builds, staged Linux validation verifies PIE/RELRO/NOW/stack state with `readelf`, native Wayland PR CI runs the assetless renderer smoke under headless Weston with libdecor enabled and disabled, native Wayland VRAM detection avoids optional X11 probing, and `OPENQ4_WAYLAND_DISABLE_LIBDECOR=1` is available for compositor/libdecor troubleshooting.
- [x] Native Wayland CI now exercises a real SDL3 window lifecycle under Weston, including fullscreen and windowed `vid_restart` transitions, compositor-accepted window state logging, and the optional `OPENQ4_WAYLAND_SYNC_WINDOW_OPS=1` path.
- [x] Native Wayland input validation is deeper: the SDL3 backend now has a command-driven mouse-capture diagnostic that toggles relative mouse mode, reports grab/capture state, and runs in the Weston CI matrix beside the window lifecycle smoke.
- [x] Native Wayland display reporting is now covered by runtime CI: the Weston matrix runs the SDL3 display diagnostics command and checks compositor-reported displays, content scale, orientation, mode metadata, and selected-display resolution.
- [x] Linux fallback display validation is broader: the X11/Xvfb ARM64 runtime smoke now runs SDL3 display diagnostics too, and driver-specific renderer cases are filtered out of default local matrix runs unless the matching SDL video driver is selected.
- [x] The project-specific XWayland escape hatch is now runtime-validated: CI launches the staged client with `OPENQ4_FORCE_X11=1` and checks that SDL selects the X11 driver while display diagnostics still report correctly.
- [x] Game-library source staging is now auditable: openQ4 treats openQ4-game as the canonical source-input repository, stages only regular source files into `.tmp/gamelibs_stage/`, writes a hash/git-state manifest, requires that manifest at Meson configure time, and validates the staging script in local and CI smoke tests.
- [x] The multiplayer settings menu shows the selected player model preview again when the menu opens or the model choice changes.
- [x] Cinematic letterboxing now updates when the window or display size changes, and ultrawide/tall layouts add the needed pillarbox or letterbox bars so the visible cinematic area remains 16:9.
- [x] Single-player now offers a harsher Warlord difficulty above General, with beefier enemies and wider-range AI pressure, plus a Turbo Mode toggle that raises player run speed to a Quake 2-like pace, doubles vehicle top speed, doubles doors, lifts, conveyors, and other mover speeds, and removes weapon clip reload interruptions without changing weapon damage.
- [x] Starting a new single-player game from the menu now routes through a deferred session map-start command instead of executing a raw map load inside the GUI click handler, preventing the menu-window access violation seen after pressing Start Game while still entering gameplay directly.
- [x] The System settings page now has a Performance Preset dropdown and Auto-Detect button above the display controls, giving low-power systems such as Raspberry Pi a one-click profile that lowers resolution scale, AA, texture pressure, sound-shader sample admission, and surround/EAX audio cost without using debug visual skips. Auto-Detect now ignores missing or implausible memory telemetry, and Windows uses DXGI video-memory detection instead of feeding a hard-coded 100 TB VRAM value into the preset picker. The System-menu bridge now forwards preset command arguments through the same engine validation path used by the console, and settings-menu coverage keeps the GUI dropdown, localized choice labels, registry, user docs, and engine command list synchronized. The matching console commands are validated by `performancePresetSelfTest`, now recover from invalid stored preset cvars by returning to Balanced, reject explicit unknown preset names, extra preset arguments, and stray auto-detect arguments without mutating settings, require every preset write to be declared for backup/restore coverage, restore touched cvar values/flags and the global modified-state mask after validation, preflight target cvars before applying a profile, enforce coherent preset progression, keep optional shadow maps/post effects opt-in, call out `vid_restart` plus `s_restart` for full application, and still restore the higher audio targets when switching back to Balanced/Quality/Ultra instead of leaving low-power speaker/EAX/channel limits behind.
- [x] Startup installation failures now show a Quake 3-style system-console status strip with the fatal message pinned above the log, and asset/package errors clearly separate openQ4 runtime files in `baseoq4` from retail Quake 4 media PK4s in `q4base`.
- [x] Tournament spectator pre-game HUD text is centered on the main 640-wide HUD canvas again, while its black header bar now stretches across the full widescreen/ultrawide display.
- [x] Strogg flyer and fighter destruction now keeps the retail death effect visible after the aircraft is removed and restores the BSE debris handoff for the authored explosion chunks.
- [x] Mod discovery now fails clearly on bad manifests: `mod.json` parsing reports malformed manifests, tolerates unknown valid JSON fields, rejects trailing junk, and skips invalid mods with visible warnings instead of falling back to legacy directory probing.
- [x] Packaged openQ4 runtime content now has a stable pure-server identity: the build compiles openQ4 runtime PK4s before the engine, embeds their MD5 checksums for startup validation, always treats them as pure runtime packs, and release packaging reuses the staged packs while rejecting game-code/addon marker files that would change that role.
- [x] Level-related openQ4 content now ships as its own pure runtime pack: source content is split into `content/baseoq4/pak0/` for core runtime overrides and `content/baseoq4/pak1/` for light grids, map scripts, and level loadscreens; startup validates both `baseoq4/pak0.pk4` and `baseoq4/pak1.pk4`, and the default VS Code build now uses a fast compile + incremental stage path so untouched code and PK4s are skipped during everyday local/agent builds.
- [x] Experimental macOS SDL3 renderer shutdown and error paths are safer: the default SDL3 backend now checks for a live, current OpenGL window/context before screen changes, swap, teardown, or `glFinish`, rejects empty OpenGL extension lookups before calling SDL, uses overflow-safe selected-display viewport math, clamps malformed mouse movement, wheel, controller, and rumble values before integer conversion or event queuing, normalizes unusual app-entry argument state, guards POSIX clipboard/console allocation copies plus null fatal-error formats, falls back cleanly when Cocoa display enumeration is partial, and has validation coverage to keep those detached-context and null-pointer crash guards in place.
- [x] macOS fallback support is harder to crash during bring-up: the native Cocoa/OpenGL path now records a valid CGL context, validates pixel-format/context/window setup before use, releases a created context if the final make-current step fails, checks Cocoa screen and display-mode array bounds before `objectAtIndex:` calls, fails cleanly on fullscreen setup errors, unwinds partial display captures, treats missing or invalid-display VRAM telemetry as non-fatal, guards nil event helper entry points plus mouse capture, scroll-wheel overflow, and display/gamma-table lifetime paths, verifies the OpenGL context is current before swap/pause/resume/activation work, avoids stack allocation during extension lookup, returns clipboard text through the engine allocator, owns localized string fallbacks, displays fatal Cocoa alerts through a safe format wrapper, keeps obsolete Carbon/Xcode-era macOS sources out of the Meson build manifest, rejects raw C string builders from live macOS backend sources, and has validation coverage for the old undefined-return, transient-pointer, allocator-mismatch, allocation, overflow, stale-context, and null-pointer startup/shutdown hazards.
- [x] Experimental macOS POSIX support paths now reject more bad runtime state before it can crash: thread and trigger-event helpers guard invalid indexes in release builds, pthread setup avoids uninitialized attribute use, requires error-checking mutex attributes before enabling critical sections, and tracks mutex/condition initialization before later lock/wait/signal calls, thread creation validates null function/tracking arguments and negative tracking counts before registering handles, terminal and desktop-console input cursors are clamped before pointer arithmetic, console scroll-wheel deltas clamp and saturate before mutating scroll state, console command and input events check allocation and oversized input before copying, null debug/print format strings are ignored or normalized, signal-number reporting avoids signed-overflow edge cases, and process handoff survives an unexpected null environment pointer from the macOS runtime.
- [x] macOS local handoff paths are tighter: URL opening now allows only normal web links plus existing local files under openQ4 runtime roots, process handoff requires an absolute executable file path instead of searching `PATH`, strips dynamic-loader injection variables such as `DYLD_*`/`LD_PRELOAD` before launching the child process, and native fatal-error dialogs use the modern `NSAlert` API.
- [x] macOS static policy validation now guards the remaining platform-risk boundaries: validation rejects PATH-based process spawning, shell process helpers, broad AppKit URL opening, deprecated `NSRunAlertPanel`, Carbon leaking into the SDL3 path, unconditional Apple OpenAL linkage, and release workflows missing Developer ID/notarization/stapling while documenting the native fallback's legacy `NSOpenGL`/Carbon exceptions.
- [x] Experimental macOS hardware signoff now has a structured Apple-host workflow action: `Invoke-openQ4MacOSWorkflow.ps1 -Action Signoff -MacOSGraphicsBridge both` builds the OpenGL and Metal bridge variants in separate Meson build directories, runs the real-asset smoke profile and macOS renderer matrix for each, installs the Desktop launcher, records the selected OpenAL provider, captures host/display/audio/USB/Bluetooth inventory, writes bridge-specific `macos-runtime-signoff.md` reports with the remaining manual input, audio, display-mode, and in-game renderer checks, and copies the matching result directories back to `.tmp/macos-vm/results/openq4-macos-results-<run-id>.tar.gz` for review on the host.
- [x] Collected experimental macOS hardware signoff archives now validate on the host immediately after copy-back: `tools/macos/validate_signoff_archive.py` rejects unsafe tar entries, requires both OpenGL and Metal bridge reports, checks workflow logs plus renderer smoke/matrix output, `-Action CollectResults -MacOSRunId <id>` can re-collect completed reports without rebuilding, and `-RequireCompletedSignoffChecklist` can fail final evidence if any manual hardware checklist item remains unchecked.
- [x] Experimental macOS release packaging now has a Developer ID/notarization lane for official artifacts: manual release jobs import the Developer ID Application certificate from secrets, sign with the Hardened Runtime, submit the package payload through `notarytool`, staple the app ticket, and verify the result with Developer ID authority, `spctl`, and `xcrun stapler validate` checks while keeping ad-hoc signing available for local package bring-up.
- [x] Experimental macOS release entitlement handling is now explicit and fail-fast: official releases use Hardened Runtime without custom entitlements by default, optional entitlement files must parse as plist dictionaries, and App Sandbox or `get-task-allow` entitlements are rejected until a reviewed sandbox/file-access design exists for user-selected assets, saves, logs, and staged overlays.
- [x] Credentialed experimental macOS release artifacts now use compressed DMG images for the OpenGL and Metal bridge packages: the package helper creates the DMG with `hdiutil`, official signed release jobs notarize and staple the final DMG after the app bundle is signed/stapled, the workflow verifies the image before upload, and player docs explain that `openQ4.app` must stay beside `baseoq4/` and the staged runtime files.
- [x] Experimental macOS OpenAL migration now has an explicit build switch: release and validation workflows pin the current Apple framework provider, while `-Dmacos_openal_provider=system` lets local Mac bring-up test a system/OpenAL Soft dependency with `AL/...` headers before the default audio provider changes.
- [x] The companion openQ4-GameLibs repository now has an experimental standalone macOS Meson path and CI coverage: Darwin/Clang builds use macOS defines, PIC game idlib linkage, `.dylib` game modules, and package-relative `@loader_path` install names, while the new macOS workflow builds the modules and verifies install names beside a Windows/MSVC `.dll` build leg.
- [x] The Apple Silicon/arm64-only experimental macOS support policy is now explicit in build, platform, release-package, and player setup docs: Intel Mac and universal2 packages are not advertised until a dedicated runner or universal release lane exists with architecture-specific validation.
- [x] Experimental macOS SDL3 release builds now avoid linking the legacy Carbon framework: the Meson Darwin framework list keeps Carbon only for the native Cocoa/OpenGL fallback, documentation describes the split, and the macOS bridge validation test guards against Carbon leaking back into the default SDL3 package path.
- [x] Experimental macOS debugging now has a compliant test path outside the normal CI matrix: agents are directed to an Apple-hardware/SSH workflow for persistent VM or hosted Mac testing, `E:\ISO\macos\` is reserved for macOS media inventory, and a manual **macOS Debug** GitHub Actions workflow can build OpenGL/Metal bridge variants and upload logs/artifacts while a dedicated Mac host is being provisioned.
- [x] Text asset loading and console/server string handling are more crash-resistant: ASE and Maya ASCII model import now reject oversized tokens, unsafe counts, and out-of-range mesh indices, while redirects, key binds, map/cinematic paths, checksum replies, and loading GUI paths avoid fixed-buffer overflows.
- [x] Diagnostics, clipboard, config, and UDP/SOCKS paths are harder to crash with oversized input: file float-string output, JPEG messages, Windows fatal errors and clipboard text, CVar value tables, GUI debug text, stub command lines, and UDP/SOCKS packet handling now use bounded writes or explicit size rejection.
- [x] More content/tooling string paths now handle unusual input safely: command arguments, parser and lexer diagnostics, exception text, ARB program loading, cube-image combining, download titles, and precache command writers avoid fixed-buffer writes or stack-sized file copies.
- [x] Additional UI and renderer utility paths now reject or contain malformed input: Base64 assignment reuses storage safely, rectangle/debug strings are bounded, download progress avoids zero-byte ETA division, view-note commands escape quoted user text, screenshot/envshot filenames are built dynamically, ambient-map output allocations are size-checked, and Hell Bust-Out board images must match the expected dimensions before copying.
- [x] Shared formatting and diagnostic paths are more resilient to oversized input: `idStr::snPrintf` and `va` no longer format through unbounded fixed buffers, timing reports avoid zero-frame division and fixed report buffers, model precache commands build dynamically, AF vector text clamps precision and long joint names safely, GLSL info logs are capped and heap-backed, and image/material diagnostics no longer size stack allocations from runtime counts.
- [x] Windows x64 game-module loading and builds are safer: SP/MP modules now build as `game-sp_x64` and `game-mp_x64`, allocator/event/save paths avoid pointer truncation, game DLL lookup is limited to staged local runtime directories instead of PK4/search-path extraction, and Windows symbol diagnostics keep full pointer-width addresses.
- [x] Additional x64 runtime paths now keep full pointer-width addresses: Raven heap page accounting no longer truncates allocation addresses, GameLibs report modern Linux/macOS CPU targets with pointer-width stack alignment, and legacy Linux OSS/ALSA writes keep their mix-buffer pointers intact.
- [x] More x64 migration hazards are removed: shared block allocators use explicit field offsets instead of int-truncated null-pointer math, queue helpers carry field offsets with pointer-width types, legacy renderdemo playback no longer writes archived 32-bit pointer values into live renderer structures, and GameLibs keep dynamically loaded tool-DLL handles pointer-width.
- [x] Experimental macOS release archives now get stricter integrity checks before publication: packaging rejects duplicate or unsafe archive paths, symlink entries, group/other-writable and special-mode files, stale mismatched game modules, broken root/app version manifests, and incomplete localized `InfoPlist.strings` resources.
- [x] Experimental macOS release packages now validate the app bundle more like a real Apple application: generated bundles include `PkgInfo`, display-name and game-category metadata, release validation rejects symlink payloads, verifies Mach-O architecture with `lipo`, applies and verifies ad-hoc code signatures, and checks package-relative game-module install names in both packaging and manual release CI.
- [x] Experimental macOS packages and staged builds are harder to ship in a broken or unsafe state: builds add stack/fortify/linker hardening, game modules use package-relative install names, dylib loads use canonical local-scope paths, release archives preserve executable bits for loose game modules, and validation rejects quarantine flags, debug bundles, stale non-runtime artifacts, unsafe archive metadata, and missing `.dylib` payloads before publication.
- [x] macOS host/guest testing is safer and more deterministic: SSH sync now uses per-run remote temp directories, source and asset archives reject unsafe paths, links, and special files, personal config/key files stay out of asset transfers, guest builds cannot wipe outside the repo, launcher paths are shell-quoted, wrong-architecture client launches require an explicit opt-in, and package PK4/archive creation rejects link or non-regular payloads.
- [x] Experimental macOS package and hardware-signoff evidence is stricter before release: staged payloads and unsigned archives now reject Finder/AppleDouble metadata such as `.DS_Store`, `._*`, and `__MACOSX`, signoff result archives reject duplicate or malformed path entries, host/guest tar extraction rejects hardlinks before unpacking, and custom macOS asset installs keep Quake 4 config, log, and key files excluded.
- [x] Experimental macOS hardware signoff now validates the staged runtime before smoke tests: the guest workflow fails early on missing or non-executable client/dedicated/game-module payloads, stale Windows/Linux modules, Finder/debug metadata, or architecture mismatches, records `lipo -archs` evidence in the report, rejects oversized signoff text members on the host, and verifies copied Quake 4 assets contain q4base PK4s before running gameplay validation.
- [x] macOS signoff collection is stricter and less prone to stale evidence: manually supplied asset roots are checked for q4base PK4s before smoke runs, staged installs reject additional Apple metadata folders such as `.Spotlight-V100` and `.Trashes`, collected result archives include only the expected `signoff` bridge directories, and the host validator rejects unexpected top-level result directories.
- [x] macOS guest signoff runs fail earlier on malformed workflow inputs: action, graphics bridge, OpenAL provider, build type, and platform backend tokens are validated before result directories or Meson setup arguments are created, keeping bad environment values from producing misleading signoff folders or logs.
- [x] macOS host workflow cleanup is more reliable: the SSH runner now removes its per-run `/tmp/openq4-macos-<guid>` transfer directory from the Apple host in a `finally` block, while refusing to remove any path that does not match the expected temp-root pattern.
- [x] macOS host/guest workflow paths and limits are more robust: documented `~/openq4-work` defaults now expand to the Apple user's home directory inside remote transfer, asset, build, and result-collection scripts; host parameters reject invalid ports, build types, empty build directories, and non-positive smoke limits; guest Meson setup refuses source/runtime/tool directories as build outputs; and signoff archives reject oversized non-text payloads or malformed action tokens.
- [x] macOS runtime handoff paths are more defensive: `Sys_OpenURL` now rejects overlong or control-character URLs before logging them, requires HTTP(S) URLs to include a host, keeps file URLs constrained to clean local runtime files, and `Sys_StartProcess` fails clearly if an executable bit cannot be applied before `execve`/`posix_spawn`; the macOS bootstrap also checks for `xcrun`, `plutil`, `lipo`, `otool`, and `codesign` before build/signoff work starts.
- [x] Experimental macOS package metadata validation is stricter before artifacts are published: app bundle validation rejects empty icon/version/plist payloads and non-dictionary `Info.plist` roots, bundle creation fails immediately when no staged icon is available, localized `InfoPlist.strings` files are parsed for malformed or duplicate keys instead of substring-matched, and archive metadata members are capped so malformed packages cannot hide oversized app metadata.
- [x] Sound-reactive world lights now match stock Quake 4 behavior more closely: hum-driven map lights such as Air Defense 1's corridor lighting use the authored sound-shader flicker envelope instead of dropping to black.
- [x] Single-player enemy-hit feedback now matches retail Quake 4 again: the stock cursor GUI's per-component color aliases resolve correctly, so damaging enemies flashes the crosshair red without requiring custom HUD content or a new setting.
- [x] Display resolution control is now display-aware in the Settings menu: the System page lists Desktop Native, only SDL3-reported fullscreen preset resolutions for the selected monitor, then Custom; exact detected modes remain selected instead of relabeling as Custom, refresh choices follow the selected resolution, the legacy `r_mode` catalog now covers common display resolutions from older 4:3 panels through ultrawide/HiDPI/8K modes while preserving existing mode IDs, and high-refresh requests above 200 Hz are accepted.
- [x] Language selection in the settings menu now applies immediately and keeps non-English menus readable: the engine handles the menu's language reload commands, refreshes the current language index, parses translated choice labels without punctuation warnings, and falls back through English strings when a translated pack is missing an openQ4 entry.
- [x] Settings menu brightness and gamma changes now visibly apply on SDL3 and legacy non-native gamma backends through a final framebuffer color-mapping pass, while native Linux and experimental macOS gamma-ramp backends keep using their OS display ramp path.
- [x] Startup and module-switch loading screens keep their Quake 4 background artwork visible again by emitting the dark fill and splash art as separate fullscreen GUI batches, so stock material sorting can no longer cover the image.
- [x] Switching between single-player and multiplayer now keeps the game window open and shows the existing initialization-style loading screen while the matching game module is reloaded, avoiding the old console flash and window recreation during SP/MP map transitions.
- [x] Headless renderer jobs can now run with `r_hiddenWindow 1`, keeping the GPU-backed OpenGL context needed for light-grid baking while hiding the game window; the all-MP light-grid bake task now opts into that mode, starts directly in the multiplayer game module, and enables the cheat-gated bake path.
- [x] Baked light-grid coverage now includes the remaining stock multiplayer map families outside `mp/q4dm*`, so CTF, DeadZone, Tourney, q4cmp, and expansion arenas ship with generated irradiance-volume data under `baseoq4`.
- [x] Baked light grids now produce a map-local `.lightgridpack` runtime artifact that keeps probe metadata and per-area binary image chunks together, reducing loose-file lookup overhead while preserving current-area/neighbor residency and keeping `.lightgrid` plus TGA outputs as fallback/debug files.
- [x] Packed light-grid residency now keeps every current-frame receiver-referenced grid resident before aging unused loose fallback areas, and materialization timing only prints through `r_lightGridReport`; this stops Air Defense 1's repeated area materialization console spam without forcing packed grids back through runtime streaming.
- [x] Baked light grids now visibly affect world geometry: the indirect pass runs after opaque ambient/material drawing, submits receiver geometry through the stable baked-irradiance path instead of depending on fragile material albedo stages, and exposes gated receiver diagnostics through `r_lightGridReport`.
- [x] Renderer diagnostics now include `r_skipEntities`, which keeps BSP world area models while suppressing non-world render entities for clean world-only light-grid A/B captures.
- [x] Baked light-grid composition now hands modern visible color/depth back before legacy overlay passes, uses a tolerant overlay depth test against copied modern depth, and decodes AG-packed bump maps like the main material path so indirect irradiance samples the expected world normals.
- [x] Baked light-grid contribution is now color-managed before scene blending: 8-bit probe atlases are gamma-decoded, receiver color comes from representative material albedo instead of a white overlay, the default pass uses exact depth with no additive polygon-offset overdraw, and `r_lightGridIntensity`, `r_lightGridVisibilityFloor`, `r_lightGridIrradianceGamma`, and `r_lightGridMaxContribution` keep the result tunable for SDR, HDR, and bloom.
- [x] Machinegun scope lighting now stays consistent when irradiance volumes are enabled: first-person weapon scope/glass surfaces with authored color-mask stages are left to their normal material and GUI light instead of being washed out by the additive light-grid pass.
- [x] Optimized Linux builds keep baked light-grid receivers stable: world surfaces now use only their own resolved portal-area light grid instead of borrowing the camera area's grid, and GCC/Clang builds avoid strict-aliasing assumptions in the legacy renderer/game code.
- [x] The machinegun zoom scope now restores the retail blur effect while keeping widescreen and tallscreen projection correct: the scope samples the captured scene by active viewport coordinates instead of stretching the 640x480 GUI canvas, and aspect-expanded gutters use matching blur masks.
- [x] The nailgun zoom scope now fills widescreen and tallscreen gutters with matching expanded scope artwork, preserving the stock lock-on reticle while avoiding exposed safe-area edges.
- [x] GitHub releases now broadcast curated openQ4 announcements to Discord from both the manual release workflow and manually published GitHub releases, using release-note highlights plus platform download links while keeping the webhook URL in a repository secret.
- [x] Steam Deck and SteamOS SDL support is closer to Windows SDL parity: SDL lifecycle events are watched as soon as SDL receives them so suspend/resume, foreground/background, and low-memory notifications cannot be missed between normal event-pump frames, direct-launch Deck/SteamOS profile detection accepts more host signal aliases, and the launcher regression test now uses the real case-sensitive Linux template path.
- [x] SDL3 input support is aligned across Windows, Linux, and macOS: keyboard/text input, relative mouse capture, high-resolution wheel handling, gamepad/joystick hotplug, stable controller binds, rumble, battery diagnostics, gyro, touchpad, touchscreen routing, and `listControllers` diagnostics now share the same backend contract on SDL3 platforms, with macOS still tracked as experimental support.
- [x] Single-player loading screens now consume the button, key, or controller input used to continue before gameplay resumes, so pressing fire or another bound action to leave the prompt no longer triggers that action in the first in-game frame.
- [x] Linux and macOS memory handling now matches the Windows contract more closely: protected allocator blocks use real page-lock and unlock calls instead of no-op success stubs, and reported system RAM totals use the same rounded, overflow-safe megabyte reporting across Linux and experimental macOS builds.
- [x] macOS renderer startup now validates the exact callable OpenGL entry points used after SDL3 creates Apple's fallback compatibility context, including multitexture calls, VBO subdata uploads, optional PBO readbacks, ARB program attribute/local-parameter hooks, and GLSL uniform/program hooks; this addresses the earlier crashes reported in GitHub issue #73 comments 4730727130, 4749280134, and 4749378065 by downgrading optional upload/readback paths or failing cleanly when required ARB2 hooks are missing.
- [x] Apple OpenGL 2.1 compatibility launches now disable the legacy VBO vertex cache and use the CPU-backed vertex cache instead, avoiding the renderer-upload/ARB2 crash still reported on Apple Silicon in GitHub issue #73 after the initial entry-point guards.
- [x] Optional renderer buffer-object cleanup and PBO readbacks now follow the same macOS-safe capability gates: disabled upload bridges report zero live buffers, stale buffer names are dropped when delete entry points are unavailable, and HDR/light-grid async readbacks fall back instead of bypassing the validated PBO path.
- [x] macOS ARB2 interaction draws now pass VBO byte offsets through explicit `idDrawVert` layout constants instead of forming member addresses from the vertex-cache offset token, avoiding the Apple Silicon compatibility-context crash that could still occur after the entry-point guards in GitHub issue #73.
- [x] Apple OpenGL 2.1 compatibility now skips the full ARB interaction shader upload and automatically uses the simpler interaction shader pair, avoiding the Metal bridge startup crash reported immediately after interaction color-mode detection while preserving normal ARB2 interaction rendering on other drivers.
- [x] Apple OpenGL 2.1 compatibility now bypasses ARB2 light interactions after the July 3 issue #73 follow-up report (comment 4874832470) showed the M4 Max path still crashed after renderer startup completed; affected macOS launches log `ARB2 interaction driver bypass` and use the existing ambient rescue floor as a degraded-lighting fallback instead of entering the fragile first-frame interaction draw.
- [x] Experimental macOS package staging now pins game-module outputs to the `.dylib` names that the app bundle, release archives, and runtime validators require, so OpenGL and Metal bridge packages cannot silently drift to non-macOS module suffixes.
- [x] Platform macro guards were hardened so legacy `MACOS_X`, `__MACH__`, and `__MWERKS__` checks use defined-style preprocessor tests, shared feature switches provide guarded 0/1 defaults, and push validation now blocks those brittle guard forms from returning.
- [x] Linux x64 and ARM64 CI builds are unblocked again: SDL3 DRM VRAM detection now uses openQ4's portable string comparison path, keeping sysfs VRAM probing intact without tripping the guarded C string wrappers on GCC/Clang builds.
- [x] Multi-display and window behavior is now aligned across Windows, Linux, and experimental macOS SDL3 builds: selected-monitor launches, desktop/exclusive fullscreen, borderless windows, selected-display spanned UI, windowed placement, high-DPI drawable refresh, and display-change recovery use the shared SDL3 path, while macOS now honors the selected display for compatibility calls and falls back through current mode/display bounds when desktop mode is unavailable.
- [x] Strogg health stations are usable again after the player becomes Strogg in the single-player campaign: animated in-world GUI models now participate in renderer GUI tracing, so the station panel can receive the stock `heal begin` / `heal end` commands.
- [x] Strogg health-station panels now mask their animated cell strip like retail Quake 4: authored GUI background materials keep the GUI emission order instead of sorting over the station faceplate.
- [x] Experimental macOS package launches are more robust from Finder and Terminal: SDL3 startup now enters the engine through `SDL_RunApp()` so platform-specific launch setup runs before early engine initialization, macOS base-path discovery resolves the actual executable path and validates `baseoq4/` before selecting a default, and the generated `.app` embeds the packaged client as its native app executable.
- [x] macOS app base-path discovery no longer depends only on Cocoa bundle metadata: when the executable path is inside `openQ4.app/Contents/MacOS`, the engine now probes the extracted package root directly before falling back to `NSBundle` paths.
- [x] macOS first-launch save/config handling is more robust: the default `~/Library/Application Support/openQ4` save root is normalized, created recursively with private permissions when missing, verified as writable/searchable before use, and falls back through the current working directory with clear diagnostics instead of silently selecting an unusable support-log path.
- [x] Native macOS OpenGL context deactivation is safer during renderer skip/restart diagnostics: deactivation now tolerates missing contexts and only clears openQ4's own Cocoa context, avoiding accidental state disruption when Cocoa has no active game context.
- [x] macOS URL and process handoff diagnostics are safer for support logs: rejected URLs are no longer echoed before UTF-8 parsing, Foundation parsing, and the runtime-root/scheme allowlist pass, failed AppKit URL handoffs use a generic diagnostic, and malformed process command lines are rejected without copying the rejected command into logs.
- [x] Experimental macOS support is covered earlier in CI: pull-request and push validation now build/install both macOS ARM64 SDL3 OpenGL and Metal bridge variants, staged macOS payloads verify their icon and splash assets, and release packaging validates the generated `.app` executable and `Info.plist` before archiving.
- [x] Experimental macOS packages now advertise the same macOS 11.0 minimum version that Meson builds target, avoiding an accidental macOS 12 metadata floor for users on supported Big Sur systems.
- [x] macOS app launch diagnostics are clearer: the generated `.app` now embeds the packaged client binary directly, release CI checks that embedded executable against the adjacent package client plus key plist values, and executable-path discovery handles long `_NSGetExecutablePath` buffers without silently deriving paths from truncated strings.
- [x] Experimental macOS release packages now reject unbundled non-system runtime dependencies: app, client, dedicated-server, and game-module binaries are checked with `otool -L` during release packaging so accidental Homebrew/local/`@rpath` links fail before users hit a launch-time loader crash.
- [x] Final experimental macOS package validation now checks the packaged runtime source for required app bundle entries, executable bits, localized plist resources, safe package-relative paths, and matching `Info.plist` identity/version values before the release container is published.
- [x] Experimental macOS build and validation entrypoints no longer depend on GNU-style `dirname --`, so the Bash wrappers used by macOS CI and release packaging bootstrap cleanly on the stock BSD userland.
- [x] Legacy macOS app metadata is aligned with the generated release bundle: the in-tree `Info.plist` now uses `openQ4.icns`, declares `CFBundleName`, carries a short version, and is parsed by the macOS regression test against the same identity contract as packaged apps.
- [x] macOS staged-payload validation is now architecture-aware: release and local validators require matching no-extension client/dedicated binaries, matching `game-sp`/`game-mp` `.dylib` modules, and reject stale `.dll`/`.so` game modules before packaging.
- [x] Experimental macOS release instructions now call out the supported `openQ4.app` launch path and how to clear Gatekeeper quarantine for unsigned archive builds, making OS-level launch blocking easier to distinguish from engine startup failures.
- [x] Renderer performance promotion evidence is harder to overclaim: a new `renderer_performance_summary.py` tool compares candidate benchmark runs against explicit ARB2 across the agreed gameplay scenes, records P50/P95/P99/max frame time and pacing evidence, and keeps `perf=arb2-or-better` failed until every scene clears the review threshold.
- [x] Renderer candidate selection is harder to overclaim: a new `renderer_candidate_summary.py` tool records the measured modern path, candidate alternatives, acceptance thresholds, required promotion gates, and the current disqualification reason before any renderer path can be treated as a default-promotion candidate.
- [x] Renderer portability evidence is harder to overclaim: a new `renderer_platform_summary.py` tool records Windows x64, Linux x64, and macOS GL 4.1 report paths plus reviewed hardware, driver, GL, tier, timer, upload, DSA, multibind, and GPU-driven metadata before a platform can be treated as complete.
- [x] Approved visual-reference evidence is easier to review without adding screenshot binaries to the repo: a new `renderer_visual_reference_summary.py` tool defines the long-term visual matrix, writes a lightweight manifest template, checks reference-backed gameplay reports and RMS/max thresholds, and records the required human-review checklist before `visual=pass` can be considered.
- [x] Modern shader-library maintenance is easier to audit without changing packages: a new `renderer_shader_inventory.py` tool checks the internal shader-source inventory, GLSL tier coverage, program-slot capacity, draw-record safety guard, validation cases, and reload gate, while keeping runtime shader sources compiled into the engine instead of adding loose shader files.
- [x] RenderDoc/API renderer evidence is easier to collect consistently: a new `renderer_capture_summary.py` tool defines the long-term capture matrix, expected `.rdc` and API-trace file names, required platform/GL metadata, cvar observations, and review checks before any RenderDoc, bind-count, or graph-invalidation claim can be treated as complete.
- [x] Renderer promotion evidence is easier to audit without changing defaults: a new `renderer_promotion_evidence.py` tool gathers safe validation, SP/MP gameplay, visual, capture, performance, presentation, rollback, default-safety, and platform reports into one blocked-or-ready summary before any `r_rendererModernAutoPromote` sign-off can be considered.
- [x] Dedicated server renderer startup is safer to validate: the safe matrix now has a dedicated-server case that proves the disabled BSE runtime manager path still uses the real effect-decl allocator, loads the MP game module, and shuts down renderer scaffolding without the previous vertex-cache NULL-free error.
- [x] Low-overhead modern-submit validation is easier to reproduce: a new `low-overhead-submit` gameplay profile runs GL 4.5 diagnostic modern submit under real maps, reports submitted masked draws, DSA and multibind activity, restore handoff counters, and zero submit fallback or missing-buffer counters, while keeping renderer defaults unchanged until API captures prove a broader benefit.
- [x] Static renderer residency checks are easier to control: a default-off `r_vertexBufferBudget` path now lets validation runs enforce an LRU budget for static vertex/index buffers, reports fixed, protected, purgable, and over-budget bytes in `gfxInfo`, and keeps normal renderer defaults unchanged.
- [x] Render-graph invalidation diagnostics are clearer during gameplay: reports now distinguish submitted discard calls from armed candidates whose owning modern pass did not submit, and a new `graph-invalidation-modern` profile validates the modern side-path cvars, GPU-timer capture, and default-vs-armed accounting without changing renderer defaults.
- [x] Renderer shutdown and restart leak checks are easier to audit: a default-off `r_rendererShutdownAudit` path now reports renderer-owned GL object counts around full `vid_restart` and final shutdown, and the safe validation matrix verifies repeated restarts return those counts to zero before release sign-off.
- [x] SP/MP mode-switch renderer checks are easier to audit: the gameplay benchmark harness now has a `mode-transition-resource-count` profile that runs single-player, multiplayer, and single-player again in one process, preserves reload logs across module switches, and reports stable renderer graph, FBO, upload-buffer, fence, and shader counts while keeping static residency visible as tracked context.
- [x] Repeated map-transition renderer checks are easier to audit: the gameplay benchmark harness now has a `map-transition-resource-count` profile that alternates storage and storage2 maps in one SP process, compares post-warm repeated-map renderer object counts, and reports static upload residency separately so content-cache warming stays visible without hiding real graph, FBO, fence, or shader leaks.
- [x] Renderer restart/residency validation is easier to audit: the safe validation matrix now compares repeated post-restart render-graph, upload/static-buffer/fence, and shader-program counts, surfacing the stable values directly in Markdown and JSON reports.
- [x] Low-overhead state-cache fallback coverage is easier to audit: the safe validation matrix now has a focused no-multibind fallback case that proves mixed 2D/cube-map texture groups use the target-aware fallback path and report fallback batches without changing renderer defaults.
- [x] Long upload-pressure checks are easier to run and audit: the benchmark harness now has an `upload-pressure-long` profile with 30-second samples and reports whether fence waits, fence timeouts, fence fallbacks, ring high-water, overflow, and frame-stall counters were captured for every role before any upload-default decision is considered.
- [x] Render-graph invalidation is safer to test: the default-off path now has pass-owned GL discard submission, self-test coverage, and diagnostics for submitted calls plus disabled, unsupported-tier, missing-owner, and missing-FBO skips, while renderer defaults stay unchanged until gameplay and capture evidence prove a benefit.
- [x] Renderer gameplay sign-off is current for the first mid-term Windows x64 tranche: the required storage, air-defense, storage2, medlabs, MCC Landing, and q4dm1 loopback SP/MP coverage now passes after the renderer profiling and reporting updates.
- [x] Renderer visual screenshot evidence is current for the first mid-term Windows x64 tranche: post-processing, BSE-heavy, GUI/subview, dense-light, and MP listen/client scenes now have preserved gameplay captures with no black-frame, post-process, GUI/subview, or obvious shadow/stencil fallback failures found in the inspected bundle; approved reference-image comparisons remain a separate promotion gate.
- [x] Renderer presentation evidence is current for the first mid-term Windows x64 tranche: storage, air-defense, storage2, medlabs, and q4dm1 loopback now have capped, uncapped, and VSync gameplay pacing captures, with no renderer failure signatures found and no default pacing policy change made from the sampled data.
- [x] Renderer performance comparison evidence is current for the first mid-term Windows x64 tranche: storage, air-defense, storage2, and medlabs now have matched default, explicit ARB2, and modern-executor-prep captures, and the mixed results keep renderer defaults unchanged until a candidate is consistently ARB2-or-better.
- [x] Renderer GPU-timer diagnostics are current for the first mid-term Windows x64 tranche: the same performance scenes now have opt-in GL timer-query captures for default, explicit ARB2, and modern-executor-prep variants, improving bottleneck evidence while keeping timer-query runs out of default pacing policy decisions.
- [x] Renderer low-overhead evidence is current for the first mid-term Windows x64 tranche: storage, air-defense, and medlabs gameplay captures now compare GL 3.3 fallback behavior against GL 4.5 low-overhead readiness, confirming the DSA graph-resource path while keeping further state-filtering changes out of defaults until draw-submission and API-capture evidence are stronger.
- [x] Render-graph invalidation evidence is current for the first mid-term Windows x64 tranche: post-processing and BSE-heavy scenes now have default-vs-armed GPU-timer captures with clean logs, while transient invalidation remains default-off until pass-owned submission counters and stronger bandwidth evidence exist.
- [x] Renderer benchmark runs are more reliable on Windows: long descriptive gameplay case names now use compact stable filesystem IDs for save, log, stdout, and stderr paths, preventing stress profiles from failing before the game launches because the log path is too long.
- [x] Renderer upload-pressure evidence is current for the first mid-term Windows x64 tranche: storage, air-defense, and medlabs scenes now have baseline and stress-profile captures for default, persistent, reduced-ring, minimum-frame-buffer, and map-range/fallback upload paths, with no default upload-policy change required from the sampled data.
- [x] Renderer safe-start validation is current for the mid-term renderer work: the broad default validation matrix now passes all 36 startup, self-test, tier, promotion-readiness, shader-tier, and presentation-safe cases after the latest renderer diagnostics and reporting updates.
- [x] Renderer platform validation is easier to audit: validation reports now summarize the actual startup platform, GL context, selected tier, debug-context state, GPU timer availability, upload path, and low-overhead DSA/multibind readiness for completed safe cases, while keeping Linux and experimental macOS promotion requirements explicit until those runs exist.
- [x] Modern renderer promotion readiness is easier to audit: the validation matrix now includes assetless startup probes that prove auto-promotion stays blocked with missing or incomplete evidence, explicit `r_renderer arb2` remains a rollback escape even with complete evidence, and default startups keep debug, validation, bindless, shader-reload, and modern-visible side paths off.
- [x] Modern renderer rollback evidence is current for the first mid-term Windows x64 tranche: a storage gameplay proof now exercises opt-in modern-visible side paths, verifies they fail closed, then returns to explicit ARB2/legacy settings with experimental renderer paths disabled.
- [x] Renderer presentation checks are easier to reproduce: the gameplay benchmark harness now has a `presentation-comparison` profile for capped, uncapped, and VSync pacing across the first mid-term SP/MP scenes, and reports now include avg/P50/P95/P99/max frame pacing in one table.
- [x] Renderer benchmark reports are easier to reproduce and debug: generated reports now record the executable, asset base path, staged dev path, game directory, save paths, cvar overrides, autoexec files, elapsed/timeout status, and exact launch commands for each benchmark role, with failed and planned runs summarized without digging through logs first.
- [x] Mid-term renderer visual checks are easier to reproduce: the gameplay benchmark harness now has a `visual-comparison` screenshot profile for post-processing, BSE-heavy, GUI/subview, dense local-light, and MP listen/client scenes, and reports now show the active reference bundle and image-diff thresholds used for optional TGA comparisons.
- [x] Renderer performance comparisons are easier to reproduce: the gameplay benchmark harness now has a `performance-comparison` profile for default, explicit ARB2, and modern-executor-prep runs across the first mid-term target scenes, and reports now surface renderer CPU phase timing, GPU timing text, draw counts, pacing, uploads, and state-cache counters in one table.
- [x] Render-graph invalidation evidence is easier to collect: the gameplay benchmark harness now has an opt-in `graph-invalidation` A/B profile that compares default and `r_rendererGraphInvalidate`-armed runs across post-processing and BSE-heavy scenes, with report tables for tagged, candidate, armed, submitted, and skipped invalidation counts.
- [x] Render-graph transient attachment optimization is easier to validate safely: resource diagnostics now classify invalidate tags into last-use candidates and skip reasons, `rendererMetrics` and benchmark reports expose those counts, and the default-off `r_rendererGraphInvalidate` path is armed for future pass-timed discard submission without changing the visible renderer.
- [x] Low-overhead renderer profiling is easier to compare: the gameplay benchmark harness now has an opt-in GL 3.3 versus GL 4.5 state/bind profile with detailed metrics for DSA updates, multibind batches, classic texture binds, graph allocation paths, upload fences, and state-cache counters.
- [x] Renderer optimization profiling is easier to reproduce: the gameplay benchmark harness now includes an opt-in upload-pressure matrix for storage, air-defense, and medlabs scenes, with reports that surface upload KB, ring high-water/overflow, stalls, fence waits/timeouts/fallbacks, upload path, and state-cache counters without manual log parsing.
- [x] Linux AI wake/sleep behavior is more reliable: SP/MP PVS visibility merging now uses fixed 32-bit words on every platform and the connected-area flood no longer reads past its queue, preventing monsters from incorrectly going dormant when they should remain connected to the player.
- [x] Multiplayer light-grid coverage now includes three-bounce bakes for every shipped `q4dm*` map, covering Q4DM1 through Q4DM11 plus Q4DM11V1, and single-player coverage now includes three-bounce Air Defense 1 (`game/airdefense1`) and Air Defense 2 (`game/airdefense2`) bakes; the light-grid receiver now replays terrain material blend layers instead of lighting only one diffuse stage, and the baker completes light-grid metadata files reliably after atlas generation.
- [x] Save-game loading is more reliable: restored trace-model caches now accept stock-compatible saved collision data without an over-strict plane check, and failed or interrupted restores clean up safely instead of crashing during map shutdown.
- [x] macOS manual releases now publish both OpenGL and Metal ARM64 packages reliably: the release workflow passes dash-prefixed package suffixes in an argument-safe form, so `-opengl` and `-metal` variants no longer fail before packaging starts.
- [x] Enemy bullet impacts now keep their resolved flesh, armor, or monster-metal hit material through replicated damage effects, so sounds, wounds, and splats follow the same hit-material result as retail Quake 4 instead of falling back to a model's base material.
- [x] Blood marks and splats now project onto visible animated models again: the renderer uses the retail dynamic-model overlay path for wounds and handles Quake 4's packed character meshes when baking overlay surfaces, so enemy flesh hits and weapon blood splats are no longer lost on collision-only geometry.
- [x] The `levelshot` capture command now writes tiles using the active mapDef `loadimage` name, so shared shots like `game/airdefense2` save as `airdefense` and split-map visits such as `first`/`second` save under their matching loadscreen names.
- [x] The `gotolevelshot` debug command now reads that matching levelshot `.txt` pose, supports explicit `first`/`second` split-map lookups, enables noclip only after a valid pose file is found, and moves the player back to the captured view.
- [x] Experimental macOS builds now retain the OpenGL package while adding a separate Metal bridge package without a native renderer rewrite: releases build both `-Dmacos_graphics_bridge=opengl` and `-Dmacos_graphics_bridge=metal` SDL3 variants, the Metal build links the Metal/QuartzCore bridge frameworks, startup logs the active graphics bridge, and the visible renderer stays on openQ4's stock-compatible OpenGL path.
- [x] The macOS Metal bridge is more robust during startup and diagnostics: the bridge define is applied consistently across macOS source languages, failed SDL Metal hint setup is logged, SDL splash/system-console support windows now require valid SDL window IDs before renderer creation, try the Metal/default renderer first while falling back to software, and log splash event preservation failures so fatal-error and startup UI remain diagnosable if the Metal renderer or SDL event queue cannot initialize cleanly.
- [x] Shadow-map contact now stays tighter on level-start lighting and grazing surfaces: receiver slope bias is bounded, projected and point defaults use lower bias, caster polygon offset is less aggressive, and the modern clustered path follows the same receiver-plane-bias policy as ARB2.
- [x] Level loads now start real map work sooner and are easier to diagnose: the loading screen no longer waits a full extra second before blocking initialization, repeated missing collision-cache probes and duplicate map media precache requests are skipped, cold-cache image generation avoids redundant source timestamp probes when no generated file exists yet, runtime image loads no longer write hundreds of megabytes of generated binary-image cache files unless `image_writeGeneratedImages` or build mode opts in, loading-screen presents temporarily bypass VSync so `r_swapInterval` cannot throttle blocking map loads, missing image fallbacks now correctly mark themselves defaulted so optional generated light-grid atlases stop retrying every frame, generated binary-image cache writes and optional light-grid misses no longer spam normal logs, and load logs now break startup time into render-world, game, collision, entity-spawn, renderer media, sound, decl, BSE, UI, and settle phases.
- [x] Starting a campaign map partway in no-save/dev-start flows now treats the authored loadout as already owned equipment: startup armor, ammo, inventory items, and weapons no longer flash pickup notifications, and the selected weapon is settled into its ready state instead of playing a pickup-style raise after the map appears.
- [x] Steam Deck setup is easier to diagnose and friendlier on battery: Steam asset discovery now accepts explicit Quake 4, Steam root, Steam library, Steam compatibility, and XDG override paths with startup logging, while the Deck profile can apply a display-refresh-aware default frame cap and low-battery rumble cap without rewriting user-tuned settings.
- [x] Steam Deck support now has a dedicated `listControllers` diagnostics command plus a shipping QA checklist, making it easier to capture SDL controller, gyro, touchpad, touchscreen, battery, Steam Input pass-through, suspend/resume, display, asset-discovery, and performance evidence before release.
- [x] Steam Deck controls are easier to tune in-game: Settings -> Game Options -> Controller now exposes gyro, gyro sensitivity, touchpad mode and sensitivity, touchscreen routing, and low-battery rumble caps with localized labels and settings-registry coverage.
- [x] Steam Deck support is now guarded by automated validation: launcher/profile activation, Steam asset discovery overrides, native SDL3 gyro/touchpad/touchscreen routing, suspend/resume recovery, frame-cap defaults, controller diagnostics, menu rows, and user/QA docs are checked in local validation and CI smoke jobs.
- [x] Steam Deck package guidance is clearer: the packaged README and getting-started guide now explain the Deck launcher, direct-launch auto-profile fallback, native Wayland default, `OPENQ4_FORCE_X11=1` fallback, in-game Deck controls, and `listControllers` diagnostics, while staged and nightly package validation check the generated Deck launcher before release.
- [x] The screen-space camera-flare post effect has been removed from renderer scheduling, settings, validation, metrics, and shader-library coverage while preserving authored material flare deform support used by stock content.
- [x] Post anti-aliasing is easier to trust: `r_postAA` now resolves through an explicit Off/SMAA 1x schedule with startup/runtime logs for requested and effective mode, source/output targets, and fallback reason, while SMAA lookup textures fail closed when dimensions, format, filter, or clamp mode do not match the embedded tables.
- [x] SMAA post anti-aliasing now samples with the actual post-process source size instead of the desktop video mode, improving correctness across render-scale and resized targets while logging the active target dimensions and falling back safely if source/output sizes drift.
- [x] SMAA post anti-aliasing now has an explicit render-target safety contract: source, edge/blend, and weights/final targets are named, checked for read/write aliasing before each pass, cleared before use, and labelled for GL debug captures when supported.
- [x] SMAA edge detection now declares its source colour-space contract explicitly, preserving the current legacy perceptual-LDR look while logging the active contract and preventing future HDR or tonemap changes from silently shifting edge thresholds.
- [x] SMAA post anti-aliasing is less dependent on legacy OpenGL state: the SMAA shaders now use explicit GLSL inputs and outputs, fullscreen passes draw through explicit geometry, and a state-poison gameplay profile guards against texture-unit and client-array leakage that could otherwise crash the driver.
- [x] SMAA post anti-aliasing now has opt-in quality modes with an explicit shader contract: the existing medium path remains `r_postAA 1`, `r_postAA 2` and `3` increase the luma search quality, `r_postAA 4` enables a colour-edge prototype for comparison captures, and the runtime log records the active edge mode, threshold, search steps, and local-contrast scale.
- [x] SMAA post anti-aliasing no longer blacks out gameplay when `r_postAA` is set to a nonzero mode; medium, high, ultra, and colour-edge prototype modes now keep the world visible through the explicit fullscreen pass.
- [x] The graphics settings menu now lists every Post AA mode by name, so players can choose Off, SMAA Medium, SMAA High, SMAA Ultra, or Color Edge without using the console, and the settings registry/docs now track the same full `0..4` range.
- [x] MSAA settings now stay on the supported quality ladder: `r_multiSamples` is bounded to `0..16`, console completion lists only Off/2x/4x/8x/16x values, unusual config values are normalized before video startup, SDL3 builds retry lower sample counts when a window or GL context rejects the requested MSAA level, and `gfxInfo` now summarizes requested/effective MSAA, GL sample caps, alpha-to-coverage, post AA mode, screen fraction, and supersampling state.
- [x] Steam Deck support is more complete: the engine can auto-select the Deck profile on SteamOS/Deck hosts, the Deck launcher no longer forces XWayland unless `OPENQ4_FORCE_X11=1` is set, SDL3 now handles native gyro, gamepad touchpad, and touchscreen input paths, and suspend/resume events release input, stop rumble, write config, and reacquire controllers on wake.
- [x] Projected shadow-map classification is now shared between the ARB2 renderer and modern shadow planner, so ordinary projected lights only use cascades when `r_shadowMapProjectedCSM` allows it while point and parallel lights keep their expected shadow-map behavior.
- [x] Projected shadow-map planning now uses the same padded clip planes, cascade fitting, atlas rectangles, split depths, and single-cascade fallback rules as the ARB2 renderer, reducing flashlight/projected-light drift between diagnostics and the visible renderer path.
- [x] Shadow-map diagnostics now validate planner descriptors and ARB2 projected-light state before use, surfacing invariant failures for cascade counts, atlas bounds, split depths, and light classification instead of letting malformed shadow data fail silently.
- [x] Shadow-map planner self-tests now compare modern descriptors against ARB2 reference state for projected, point, and cascaded lights, including the point-light six-face layout and projected clip/atlas/split metadata, catching contract drift before gameplay.
- [x] Shadow-map planning now budgets single projected, cascaded, and point lights through separate light/tile quotas, so cheap projected lights, expensive cascades, and six-face point shadows report their own capacity and throttling instead of competing in one opaque flat cap.
- [x] Dense shadow-map scenes now degrade more visibly when planner atlas budget is exhausted: budget-missed lights with drawable legacy shadow inputs become stencil fallback/cache-reuse candidates instead of silent skips, and diagnostics report fallback, stencil, cache-candidate, and true-skip counts separately.
- [x] Shadow-map planning now ages budget-throttled lights across frames, giving previously starved lights a bounded priority boost so dense scenes rotate mapped coverage instead of always selecting the same highest-priority lights.
- [x] Shadow-map diagnostics now retain per-light budget-throttling history, including the light index, reason mask, miss streak, recovered lights, and class/global budget reason counts, making dense-scene starvation visible instead of only reporting aggregate misses.
- [x] Shadow-map planning now recognizes ARB2 resident static-cache reuse separately from fresh atlas work, so diagnostics can show cacheable, cache-hit, cache-miss, fresh-update, and isolated ARB2-owned passes instead of treating all planner budget pressure as new shadow-map work.
- [x] Shadow-map diagnostics now include a reproducible synthetic projected-light scene that logs the same flashlight-style light through planner classification, ARB2 cascade/atlas/clip state, split depths, and receiver shader inputs for stable regression checks.
- [x] Shadow-map reports now expose the `r_shadowMapProjectedCSM` gate separately from global CSM counts, so planner, ARB2 summary, per-light, pass, and projected-state diagnostics agree on when ordinary projected lights cascade or stay single-tile.
- [x] Projected-light shadow transforms now carry the padded clip-plane contract through shared state, planner descriptors, ARB2 validation, and flashlight diagnostics, reducing the chance that flashlight shadows regress to raw, unpadded projection math.
- [x] Projected-light cascade fitting now records W-sign buckets, skipped samples, invalid projected samples, and named single-cascade fallback reasons, with synthetic flashlight coverage for mixed-W fallback behavior.
- [x] Flashlight projection and shadows now stay on the stock single-projection path for view-scoped weapon lights and the stock `gfx/lights/flashlight` projector, so projected shadow maps no longer camera-fit the flashlight into cascades while regular world projected lights can still use cascades when requested.
- [x] MCC Landing's post-bigdoor lift sequence now hands off smoothly to the broken replacement lift when it fails near the top of its upward ride, avoiding player trapping, fall-through, and snap correction.
- [x] Flashlight shadow parity restored: automatic quality detection now uses retail-style Quake 4 machine-spec thresholds again and repairs stale `com_machineSpec -1` configs, so capable systems classify as Ultra and the SDK flashlight shadow gate casts world shadows like retail.
- [x] Shadow-map diagnostics now match the active renderer path: modern-sidecar budget misses and ARB2 receiver-only local passes no longer report visible stencil fallback when the backend is actually rendering supported lights with shadow maps.
- [x] Shadow-map receiver fallback is easier to trust on characters and viewmodels: ARB2 pass and summary logs now split generated/skinned, custom-GLSL, and invalid receiver fallback counts, verbose logs name the stencil-filtered fallback path, and planner cache estimates account for mixed mapped-plus-fallback receiver passes.
- [x] Compatible custom GLSL-lit materials can now keep mapped shadow-map receiver lighting when they also expose ordinary stock interaction stages, while custom-only shader materials still use the stencil fallback; diagnostics report wrapped custom GLSL receivers separately and `r_shadowMapCustomGLSLReceiverWrapper` can disable the compatibility path for comparison.
- [x] Shadow-map caster admission is now documented and self-tested for perforated, translucent, GUI, subview, and dedicated collision-only materials: alpha-tested casters stay on the mapped depth path, translucent casters remain capability-gated through moment maps, view-producing GUI/subview surfaces stay excluded, and diagnostics now name dedicated collision hull rejections explicitly.
- [x] Shadow-map LOD behavior is easier to diagnose in dense scenes: mapped-caster LOD tests and rejections now flow from interaction building into ARB2 reports, modern planner descriptors, renderer metrics, and self-tests, including counters that flag when LOD-thinned lights also become budget-throttled or planner-unmapped.
- [x] Modern deferred/forward+ shadow ownership now has real projected, cascaded, point, and translucent moment lookups backed by ARB2-owned atlas bindings, while modern-visible handoff remains fail-closed whenever fallback receivers or required shadow atlas resources are not ready.
- [x] Shadow-map diagnostics now emit joined per-light rows that line up the planner decision, budget reason, caster and receiver counts, atlas layout, ARB2 pass result, receiver fallback breakdown, and modern clustered-light packed policy under the same light index.
- [x] Shadow-map diagnostics now split timing by map rendering, cache reuse, receiver mask work, receiver fallback, and full stencil fallback, with CPU, optional sync-GPU, and nonblocking timer-query buckets that make budget and fallback costs visible in gameplay logs.
- [x] Shadow-map timing no longer asserts on level start: the timed-phase helper now preserves `idTimer` construction state instead of raw-clearing timer objects before `Start()`, fixing debug-breaks in mapped-shadow level loads while leaving `r_postAA 0` untouched.
- [x] Shadow-map visual debugging now includes compare-depth delta, receiver eligibility, and fallback-reason modes for both projected and point-light shadows, making alignment problems and receiver fallback choices easier to capture through the gameplay benchmark presets.
- [x] The removed camera-flare overlay no longer contributes visibility sampling, ghost quads, or staged-overlay color paths; authored material flare cards remain on the legacy material path so their diffuse and additive glow colors stay separate.
- [x] Core engine math now uses SSE2 SIMD on x86-64 instead of scalar fallback code: character skinning, shadow-volume facing/projection, model bounds, and joint-matrix work run through a new vectorized processor (validated bit-compatible by `testSIMD`), reducing CPU cost per animated character and per shadow-casting light; `com_forceGenericSIMD 1` restores the previous scalar path.
- [x] The renderer submits work to the graphics driver with much less redundant chatter: repeated program parameters, vertex-array toggles, and vertex/index buffer rebinds are now filtered out across consecutive draws (`r_useRedundantStateFiltering`, default on), and the per-view surface sort uses a radix sort instead of the slower general-purpose C library sort.
- [x] With shadow maps disabled (the default stencil-shadow look), the renderer no longer evaluates per-surface shadow-map caster policy checks every frame, trimming front-end cost in light-dense scenes.
- [x] Remaining mixed-case project branding now consistently uses `openQ4` across UI strings, documentation, packaging templates, launch helpers, macOS resources, staged content, and the mod manifest field `requiredopenQ4Version`.
- [x] The Settings menu is clearer and more keyboard/controller friendly: the deprecated Aspect Ratio selector was removed, Screen Size now follows the active mode automatically, Resolution Scale exposes lower performance presets plus supersampling presets, Ambient Brightness accepts the full `0.0..1.0` range, and Crosshair Color is now a standard localized picker instead of mouse-only swatches.
- [x] More documented settings are now reachable from the menus: System exposes UI aspect correction, refresh-rate presets, windowed sizing, and custom exclusive-fullscreen sizing, while Game Options exposes auto-skip cinematics plus first-person weapon FOV/aspect/offset tuning; multiplayer now honors the same client weapon-offset cvars as single-player.
- [x] System quality toggles now behave like positive feature controls: Specular, Bump Mapping, and Sky show normal No/Yes choices while writing the underlying `r_skip*` cvars explicitly, and settings-page scroll bounds now match the expanded panes for mouse, keyboard, and controller paging.
- [x] Settings menu maintenance is less fragile: the Controls, System, Audio, and shared Settings popup widgets now live in their own GUI includes, creating clearer Settings boundaries while preserving the existing key-binding, video/display, defaults/auto-detect, and backend/device/volume controls.
- [x] Settings menu coverage is now metadata-driven for the refactored surface: Controls bind rows, Audio, active System rows, and all active Game Options value widgets have declarative registry records checked for widget presence, localization, bind targets/default keys, values/ranges, defaults, restart requirements, accessibility notes, duplicate direct cvar bindings, and Game Options registry completeness; the pass also fills missing French/Italian labels for active System, Controls, and Game Options rows.
- [x] Numeric settings fields are easier to type precisely: master volume and brightness accept decimal values, master volume now matches the engine's `0..1` range, Music Volume has a paired exact-entry field, and positive numeric settings filter stray text while signed adjustment fields continue to allow negative values.
- [x] Settings menu cleanup reduced legacy clutter: unreachable hidden System audio controls were removed now that Audio owns those settings directly, and coverage now guards the remaining dormant advanced-menu placeholders from being accidentally exposed.
- [x] Scripted Game Options settings are less fragile: Corpse Time, Gun Position, and Force Model now route their presets through typed menu adapters instead of duplicating cvar write chains in the GUI script, and turning Force Model off now clears its force-model cvars instead of leaving a stale `null` value.
- [x] Settings-page scrolling now behaves consistently with modern input: mouse wheels keep high-resolution fractional movement, Page Up/Page Down and controller shoulders move by page-sized jumps, Home/End jump to the start/end, modal popups no longer scroll the page behind them, and GUI scroll sliders clamp their values safely.
- [x] Long Settings panes are easier to navigate without changing any settings behavior: localized section pickers now jump Game Options directly to General, Mouse, Controller, Crosshair, Gameplay, or View Weapon and Display directly to Video, Window, Rendering, Quality, Post FX, or Sizing; both pickers follow normal scroll position changes. Audio now separates level, device, and effects rows into clearer groups while keeping the same controls.
- [x] Settings scroll panes now keep their new section controls out of the row viewport: Game Options no longer overlaps its title, section picker, and first rows, dragging a Settings scrollbar applies the scroll position immediately instead of only moving the thumb, and wheel/page scrolling no longer stalls behind the transient menu-animation active flag.
- [x] Settings page scrolling now has a single engine-owned layout path: Game Options, System, and Audio route scrollbar drags, wheel/page keys, controller paging, and section jumps through `applySettingsScroll`, which clamps the cvar, syncs the section picker, controls thumb visibility, and writes the visible row-canvas rect directly instead of replaying stale GUI transition tables.
- [x] Console and menu mouse routing no longer clamps cursor coordinates to the virtual screen or console bounds, preventing cursor snapping/sticking when using the console or moving across letterboxed, high-DPI, or edge-of-window regions.
- [x] Linux SDL3 menu cursor routing now follows the real host pointer when menu input activates, matching the already-working console cursor path and restoring reliable menu hovering and clicking on modern Linux desktops.
- [x] Linux SDL3 mouse capture now recovers reliably after opening or closing the pull-down console: POSIX frame event generation applies SDL input-frame state like Windows, and console routing is treated as the topmost mouse target before returning control to gameplay or menus.
- [x] Linux and experimental macOS SDL3 single-player loading screens now pump SDL window events while the displayed "press any key to continue" prompt is active and honor normal keyboard and printable character input, matching the expected Windows behavior after a level finishes loading. Mouse clicks now also dismiss the prompt on all SDL3 platforms: the continue gate raises a loading-input flag so the backend queues mouse-button key events even though the mouse is neither captured nor menu-routed during loading, and `tools/tests/loading_continue_input.py` locks the whole path in CI.
- [x] Linux VSync support is more consistent: SDL3 now applies and reports the requested swap interval during startup, display-mode changes, and live `r_swapInterval` edits, while the native GLX fallback now honors the same cvar through EXT/MESA/SGI swap-control extensions when available.
- [x] Linux SDL3 native Wayland startup is more robust: bundled GLEW now initializes through SDL's current OpenGL proc-address path and skips GLX-only extension probing on the SDL3 backend, preventing the post-context `Init Glew...` crash reported on CachyOS while preserving the native GLX comparison backend.
- [x] Startup language selection is more predictable: after `default.cfg`, user config, and `autoexec.cfg` run, command-line `+set sys_lang ...` is restored before the final language dictionary reload, so launch scripts can override stale archived language settings.
- [x] First-run language selection now respects installed retail language packs: openQ4 discovers available `q4base/zpak_<language>.pk4` packs, follows the OS language preference when multiple packs are present, uses the only available pack when appropriate, and repairs stale `sys_lang` values that point at missing localization media.
- [x] Linux graphics auto-detect no longer falls back to the legacy 64 MB VRAM path when modern drivers or VMs do not expose a queryable VRAM value, preventing fresh Linux configs from enabling the low-quality 256px texture downsize preset; existing affected Linux configs migrate out of that stale texture cap, GUI/font presentation atlases are protected from downsizing, and `sys_videoRam` can now be forced above 512 MB for manual validation.
- [x] SDL3 Linux VRAM autodetection is less X11-dependent: the SDL3 backend now enumerates DRM card and render-node sysfs VRAM totals before falling back to known nodes and legacy `/proc/dri`, improving native Wayland and minimal Linux sessions where optional XNVCtrl helpers are unavailable.
- [x] Linux Wayland startup guidance is clearer before SDL video initialization: mixed and pure Wayland sessions now report openQ4's `OPENQ4_FORCE_X11`, `OPENQ4_WAYLAND_PREFER_LIBDECOR`, and `OPENQ4_WAYLAND_SYNC_WINDOW_OPS` fallback/diagnostic switches instead of only pointing users at raw SDL driver variables.
- [x] SDL3 Linux desktop-resolution detection is more robust on unusual compositors: if SDL cannot report a desktop display mode, openQ4 now falls back to the current display mode and then display bounds before failing the query.
- [x] Multiplayer weapon-wheel handling no longer tries to restore the cheat-protected `timescale` cvar every frame, removing the repeated "timescale cannot be changed in multiplayer" warning while preserving normal MP weapon selection.
- [x] Startup and SP map validation logs are cleaner: stock intro loading screens now select their cinematic loading background from engine code without a mapDef override, openQ4 napalm gameplay decl replacements apply without duplicate-decl noise, stale staged intro-video material overrides no longer shadow stock assets, and optional packed-MD5R shader probes skip missing variants quietly.
- [x] Stock mappack material loading is quieter: known Quake 4 addon redeclarations between `mapobjects_mp2`, `mappack1`, and `stroyent_mp` no longer flood developer logs while unrelated duplicate material warnings remain visible.
- [x] Developer map-launch logs are less noisy during stock content validation: duplicate/tripled silhouette-edge topology diagnostics are now opt-in through `r_reportSilhouetteEdgeWarnings` instead of flooding normal `developer 1` runs.
- [x] Stock screen-wipe materials are now loaded during renderer startup, removing late `gfx/wipes/fade` and `gfx/wipes/fade_blend` decl warnings from SP/MP map-transition validation logs.
- [x] GUI text now measures the same guarded glyph extents it draws, and tight stock radio labels keep the guarded final glyph visible without expanding the small marine font atlas sampling, including the normal/vehicle HUD `incoming` and `transmission` labels, while long loading-screen map titles remain correctly aligned.
- [x] Stock GUI popup animations now keep their retail-style button labels and layered panels, fixing the exit-confirmation screen regression where the Yes/No text disappeared and the button backgrounds rendered as faint translucent strips.
- [x] Linux packaging and launch validation are stricter: staged and packaged engine binaries must remain executable, desktop entries now point at the real case-sensitive launcher names, the Steam Deck desktop entry launches `openQ4-steamdeck` correctly, Linux saves honor valid absolute `XDG_DATA_HOME`, and executable-path discovery fails clearly instead of using a truncated path.
- [x] Linux desktop setup is easier: a helper now creates an executable openQ4 launcher on the user's desktop with absolute paths to the staged runtime, icon, and optional Quake 4 asset root, and the Linux Mint VM workflow can install it through a dedicated `Launcher` action.
- [x] Linux Mint VM validation now builds and launches cleanly with the SDL3 fallback: the VM bootstrap installs a current Meson for SDL3, the Linux build wrapper honors an explicit Meson override, first-install cleanup no longer fails when `.install/` does not exist yet, and the Steam Deck launcher template path matches Linux case-sensitive filesystems.
- [x] SDL3 Linux builds no longer require X11/Xext development packages just to configure: the NVIDIA XNVCtrl VRAM probe is now an optional helper compiled only when X11 is available, while the native GLX backend keeps its explicit X11/VidMode requirements.
- [x] POSIX networking now uses modern address resolution instead of obsolete IPv4-only lookups: Linux and experimental macOS builds accept IPv6 literals and AAAA records, bind IPv4 and IPv6 UDP sockets side by side for the default local interface, and keep legacy IPv4 LAN broadcast behavior intact.
- [x] POSIX thread shutdown is now cooperative instead of cancellation-driven: engine threads can request stop through shared thread state, async/background/light-grid workers observe that request in their normal loops, and Unix shutdown no longer depends on `pthread_cancel()` reaching a cancellation point.
- [x] POSIX elapsed timing now uses a monotonic clock instead of wall-clock time, so Linux and experimental macOS frame/event deltas keep advancing steadily across system time changes.
- [x] Linux and experimental macOS SDL3 builds now have a desktop system console window matching the Windows log console path: `sys_viewlog`/`win_viewlog` can show it, dedicated or skip-renderer sessions auto-show it when SDL video is available, and the window supports scrollback, command entry, history, tab completion, copy, clear, and quit controls while keeping terminal input/output available.
- [x] Linux and experimental macOS SDL3 startup now shows and tears down the same splash bitmap used by Windows before the renderer is ready, includes that splash asset in staged non-Windows packages, falls back to a built-in startup panel if the bitmap cannot be found, hands the early SDL video subsystem to the renderer instead of reinitializing it under X11/XWayland, and recreates the SDL OpenGL window between context-tier retries so a failed GLX make-current attempt cannot poison every fallback.
- [x] Linux ARM64 is now covered by normal CI before release packaging: push and PR validation build/stage on native ARM64 runners, run an assetless renderer startup smoke under a virtual X display, and keep that smoke path bootable even when stock PK4 material or AAS declarations are intentionally absent.
- [x] The native Linux X11/GLX backend now closes its X display connection cleanly during shutdown, restoring display mode/window resources before `XCloseDisplay()` and closing the display before unloading GLX.
- [x] Loose-file lookups on case-sensitive Linux and macOS filesystems now resolve existing mixed-case directory segments instead of assuming lowercase paths, improving asset and mod discovery when shipped content casing drifts in Linux or experimental macOS builds.
- [x] Linux ARM64 release packages build again: the platform timing code now keeps x86-only CPU-frequency fallback parsing out of ARM64 compiles while continuing to use the monotonic nanosecond clock rate on non-x86 hosts.
- [x] Linux and experimental macOS runtime support is safer under normal desktop use: URL/update handoff no longer routes through a shell, Linux freedesktop URL openers work without a custom `openurl.sh`, macOS URL opens are validated before reaching `NSWorkspace`, macOS delayed process handoff now runs instead of hitting a stub, macOS GL startup fails cleanly on mode/context setup errors, long POSIX/macOS paths avoid old 256/1024-byte truncation points, free-space and memory checks use the real platform APIs, sleeps use millisecond timing without busy-looping, and POSIX networking handles DNS, strict-alignment IPv4 parsing, invalid packet sizes, socket cleanup, and nonblocking TCP edge cases more robustly.
- [x] Release packaging is better tuned per platform: stable packages now use optimized `debugoptimized` binaries with `b_ndebug=true` on Windows, Linux, and experimental macOS; Linux publishes separate detached debug-symbol archives for native crash analysis; Windows packages keep matching PDB diagnostic symbols; and Windows crashes write `crashes/openq4_crash_*.log` plus `.dmp` minidumps beside the executable for easier issue reports.
- [x] Manual release version selection is clearer: the workflow now offers `auto`, `major (x..)`, `minor (.x.)`, and `patch (..x)` choices, with patch replacing the old serial wording for third-component release bumps.
- [x] CRT Emulation no longer enables a heavy chromatic channel offset by default, fixing the red/green edge artifact that could affect gameplay, HUD, and menu output when 0.3.200 users enabled the setting; legacy archived chromatic values are capped to a subtle range.
- [x] Modern visible rendering now preserves scene-referred highlight energy in its FP16 lighting targets, restoring visible bloom extraction and HDR tonemapping/color controls when the hybrid renderer is enabled.
- [x] SSAO now darkens the final scene again under the hybrid renderer by sampling the composed scene with a world-only depth source, restoring contact darkening around wall junctions and inset details while keeping viewmodels and other foreground depth-hacked surfaces out of the effect.
- [x] Skyboxes and portal skies now remain visible when SSAO or other root-scene post-processing paths are enabled, so sky-portal scenes such as `game/airdefense1` no longer lose their sky during the final scene-target present.
- [x] SSAO no longer leaves stale material or texture state after its world-depth capture, preventing blue tinting or zebra-like striping on world models when enabled.
- [x] Supersampling in the video menu now works as a real above-native scene render instead of being clamped away: `r_screenFraction` supports safe `125%`, `150%`, and `200%` presets, clamps against GL texture limits, keeps legacy below-native scaling intact, avoids stacking MSAA onto the oversized scene target, and resolves the supersampled scene before native-resolution GUI presentation.
- [x] The `com_showFPS` overlay now uses explicit modes: `1` draws during active gameplay only, while `2` keeps the legacy always-on behavior for menus, loading, and fallback screens.
- [x] Top-right diagnostic overlays such as `com_showFPS`, frame pacing, memory, async networking, and PROC/AAS notices now draw at half size, making the readouts less intrusive during gameplay and renderer validation.
- [x] Weapon impact hit marks now appear on world surfaces again, with stock BSE decal effects honoring `g_decals` and projecting bullet-hole decals into renderer geometry correctly.
- [x] The multiplayer HUD's sheared ammo, health, and armor panels now remain visible under the modern Forward+ renderer instead of disappearing after the renderer rework.
- [x] First-person weapon model GUI readouts now remain visible under the hybrid modern-visible renderer, restoring stock on-weapon ammo/display text such as the machinegun panel without replacement assets.
- [x] Bloom bright-pass extraction now preserves saturated HDR highlights without making ordinary saturated LDR textures glow, clamps negative scratch-chain energy out of the bloom pyramid, and skips the bloom pass entirely when the intensity slider is effectively zero.
- [x] HDR tonemapping and auto exposure now sanitize scene-referred FP input before display mapping or luminance metering, preventing negative scratch-target energy from causing color inversions or exposure pumping; the tone curve uses a monotonic shoulder while preserving midrange SDR contrast, and auto exposure no longer forces the HDR scene-target path when tonemapping is disabled.
- [x] High-refresh presentation is cheaper on the CPU: animated characters and moving props no longer re-run their full CPU skinning on every repeated-state presentation frame when only their interpolated transform changed, cutting front-end cost at 240 FPS targets while keeping shadows and lighting fully correct (`r_useRepeatedStateReuse`, default on; reuse hits show as `snapshotsReused` under `r_showUpdates 1`).
- [x] HDR auto exposure no longer stalls the GPU pipeline every frame: the luminance sample is read back asynchronously with one frame of latency (`r_hdrAutoExposureAsync`, default on), removing a per-frame synchronization point that could cause frame-pacing hitches when `r_hdrAutoExposure` is enabled.
- [x] Damage overlays (wound marks) on animated models now appear on the next rendered frame instead of waiting for the model's next animation-driven refresh.
- [x] The opt-in modern renderer path is more robust: draw-plan overflow/not-ready conditions report to the console instead of silently dropping modern draws to the legacy path, shader-library debug reloads can no longer leave stale program state in the draw/submit plans, GPU validation readbacks refuse to run without GL sync objects instead of guessing readiness, material-record lookups are O(1), and per-draw matrix work moved to plan-build time.
- [x] Shader Library V2 validation now focuses on the remaining internal shader families across forced GL 3.3, 4.1, 4.3, 4.5, and 4.6 tiers, with generic shader-library cases replacing the removed camera-flare-specific probes.
- [x] Bloom, HDR, SSAO, and tonemapping now run only on the root world view instead of nested sky/mirror/camera subviews, so scene post-processing is applied once to the final frame and no longer appears attached only to subview content.
- [x] Linux startup and shutdown are more fault-tolerant: early executable-path and `/proc/cpuinfo` parsing no longer risk startup memory corruption, POSIX quit signals now exit through the main thread with the correct signal-style exit code, crash signals avoid unsafe recursive engine shutdown from the signal handler, TCP writes no longer install a temporary SIGPIPE handler, dedicated binaries no longer print client display-session guidance, openQ4's native GLX VidMode dependency is limited to the native backend, and the native GLX fallback tolerates partial window/context initialization without null-state cleanup crashes.
- [x] Linux startup now handles Quake 4 addon metadata and PK4 checksums safely, fixing the launch-time segmentation fault seen when `q4cmp_pak001.pk4` or other addon PK4s trigger lexer punctuation-table setup on GCC/Clang builds and restoring deterministic official-media validation on 64-bit Unix platforms.
- [x] Linux and experimental macOS collision/render math now uses fixed 32-bit float and sign-bit operations instead of platform-sized `long`, fixing the 64-bit Unix crash seen during single-player collision initialization on maps such as `game/airdefense1` and preventing corrupted normalization/tangent math from stretching tinted world geometry across the scene.
- [x] Linux BSE particle effects now use the same 32-bit Raven random-number sequence as Windows, fixing the oversized translucent effect sheets that could wash multiplayer scenes yellow while preserving the authored effect timing and shape.
- [x] Character heads now bind to a fully current body pose and skip invalid copied joint samples, fixing intermittent head/body misalignment seen on Linux while making SP, MP, and client-side head attachments deterministic across platforms.
- [x] Linux 64-bit game-event dispatch now preserves script, animation frame-command, entity, vector, and float arguments through the SP/MP game libraries, preventing enemy AI or animation callbacks from freezing after pointer or ABI-sensitive event data is corrupted.
- [x] Linux packages now stage the same openQ4 HUD GUI overrides as Windows, restoring the widescreen/tallscreen HUD alignment metadata for health, armor, ammo, and multiplayer overlays instead of falling back to stock 4:3 GUI scripts.
- [x] Startup asset handling now gives openQ4-style single-digit `pakN.pk4` archives priority over wider retail-style `pakNN.pk4`/`pakNNN.pk4` names in the same directory, and startup now reports a targeted error when official Quake 4 media PK4s are found under `baseoq4` instead of `q4base`.
- [x] Startup asset validation now accepts physical/DVD-style Quake 4 installs that only include the core retail media set (`pak001.pk4` through `pak022.pk4`), while still recognizing optional official patch/menu packs such as `pak023.pk4` through `pak025.pk4` when present.
- [x] Release packaging now generates `baseoq4/mod.json` from the effective engine release version, treats `requiredopenQ4Version` as a minimum compatible engine version, and rejects stable packages whose manifest version drifts, fixing the `v0.2.000` package startup failure where the splash screen appeared and the client exited before normal logging.
- [x] SMAA post anti-aliasing now keeps the resolved scene in a dedicated post-process source target, fixes the black-screen regression from copy-based scene preservation, restores stock LDR lighting parity in the post-AA render path, and marks game-level scene render targets as safe current-render feedback sources so stock heat-haze effects around fires distort the scene instead of drawing black masks.
- [x] BSE fire and smoke effects now preserve their authored tint when effect owners arrive with legacy zeroed color parameters while keeping explicit material-color effects opt-in.
- [x] Mouse input now handles modern high-DPI mice without camera stutter: the legacy hard cutoff that zeroed large view deltas is disabled by default, `m_maxMouseDelta` can restore an opt-in legacy clamp for genuinely noisy hardware, and the in-game mouse-sensitivity control now reaches sub-1 values for modern DPI tuning.
- [x] Advanced mouse tuning now covers the QuakeLive-style configuration surface while keeping openQ4's direct raw-input feel by default: the in-game Mouse Input section exposes CPI-normalized sensitivity, view filtering, signed acceleration, acceleration offset/curve, and sensitivity cap controls, while `cl_mouseAccelDebug` can write `logs/mouse.log` samples for calibration from the console.
- [x] Scoped aiming now has a dedicated Scope Sensitivity setting for mouse, gyro/touchpad mouse-look, and controller stick look; fresh configs default to 50% look speed while scoped, and the control appears in Game Options -> General beside Toggle Zoom.
- [x] Linux mouse input now maps bindable `MOUSE6` through `MOUSE8` on both SDL3 and the native X11 fallback; the SDL3 path also preserves fractional relative-motion deltas, requests unscaled relative movement where SDL supports it, and shares the same implementation source as Windows through platform wrapper files.
- [x] Linux SDL3 mouse feel is steadier on scaled high-DPI desktops: fractional relative motion now waits for whole engine counts instead of rounding early, and menu/console mouse coordinates refresh when SDL reports a display or DPI-scale change.
- [x] Cross-platform input compatibility improved beyond the SDL3 mouse path: native Linux now filters unmapped X11 keys before they enter the poll buffer, the shared POSIX poll layer ignores invalid key `0`, and native macOS keyboard/mouse input now filters unknown virtual keys, synthesizes edit-field control characters, tracks Command/Caps modifier changes, maps `MOUSE6` through `MOUSE8`, and accumulates fractional scroll-wheel deltas.
- [x] The shared SDL3 input backend now has an experimental macOS source-selection path instead of silently falling back to native Cocoa sources, so keyboard, mouse, gamepad/joystick, hotplug, and rumble handling are implemented through the same SDL3 backend on Windows, Linux, and experimental macOS. macOS hardware validation is still required before calling that path first-class.
- [x] Experimental macOS SDL3 builds now link the POSIX process, shutdown, sleep, error, and clipboard services they need instead of mixing in the legacy Cocoa app bootstrap, and SDL3 Linux/experimental macOS paste operations now use the engine allocator expected by console and edit-field callers.
- [x] Input polling is more defensive across backends: SDL3, native POSIX, dedicated stubs, and legacy Win32 now reject invalid key/mouse events, skip no-op mouse deltas, clear failed poll-return outputs, and reset controller tracking cleanly around hotplug, disable, and shutdown paths.
- [x] Controller input tuning is now available directly from the in-game settings menu, with localized controls for enabling controllers, changing stick layout, adjusting radial dead zone, look sensitivity, look curve, invert-look, trigger press threshold, and toggling or scaling rumble.
- [x] Analog controller handling now uses radial stick shaping, response-curve cvars, configurable southpaw/inverted look behavior, and immediate recalc when controller tuning changes in the menu.
- [x] Controller looking now applies sensitivity to normalized stick deflection instead of the raw `-127..127` axis value, fixing ultra-fast Xbox-style right-stick turning even when Look Sensitivity is set to the minimum.
- [x] Generic SDL joystick handling now keeps documented auto axis defaults for movement, dedicated look, and vertical/throttle input, exposes console cvars for unusual raw-axis layouts, logs joystick axis/button/hat counts on open, and adds trigger-button hysteresis so analog trigger binds do not chatter around the press threshold.
- [x] Controller look defaults now use lower sensitivity and a smoother response curve, so fresh configs and reset-defaults are less likely to feel twitchy or over-fast on analog sticks.
- [x] Controller menu navigation now feels native: focused menu buttons activate from A/Enter, D-pad and left-stick menu movement support hold-repeat, shoulder buttons page/scroll longer lists, controller users can open keybind capture without needing a mouse, and SDL3 gamepads/joysticks now receive gameplay rumble from Quake 4 sound shake/rumble metadata with `in_joystickRumble` controls that stop cleanly and avoid redundant motor refreshes.
- [x] Default controller binds now cover SDL3's menu, Guide, touchpad, trigger, stick-click, D-pad, and paddle button surface explicitly, while leaving ambiguous extra device buttons bindable but unassigned for user customization.
- [x] Bundled SDL3 is updated to 3.4.10, pulling in the latest cross-platform gamepad, joystick, Steam Controller/Triton, touchpad, sensor, and rumble fixes for the shared Windows/Linux/experimental macOS SDL3 backend.
- [x] Native Wayland compatibility improved for the SDL3 Linux backend: startup logs the selected video driver, Wayland/X11 environment, active Wayland hints, display content scale, orientation, current/desktop display modes, exact refresh details when SDL reports them, and compositor-accepted window state after screen changes; direct launches honor `OPENQ4_FORCE_X11=1` as an XWayland fallback unless an explicit SDL video-driver override is set; `OPENQ4_WAYLAND_PREFER_LIBDECOR=1` can opt into SDL's libdecor preference for compositor decoration issues; `OPENQ4_WAYLAND_SYNC_WINDOW_OPS=1` can opt into SDL's blocking window-operation synchronization for diagnosis; Wayland sessions avoid persisting compositor-owned window positions; relative mouse-look can continue when pointer confinement is unavailable; compositor-negotiated size/fullscreen changes are synchronized before renderer placement is refreshed; multi-display spanning gracefully falls back to the selected display; and `r_glTier auto` tries SDL's unversioned compatibility context before explicit profile/version contexts.
- [x] Shadow-map quality and pacing controls advanced: static projected lights can now reuse resident shadow maps with hysteresis instead of rerendering every view, update budgets account for cached reuse separately from fresh work, optional stable Poisson and projected PCSS-lite filters improve soft-shadow tuning, the experimental point-light cubemap path remains available behind `r_shadowMapPointLights 1`, and translucent shadow moments gained separate softness and light-bleed controls.
- [x] Shadow-map diagnostics and stability improved: projected and authored parallel lights now report richer caster/timing/atlas metrics with semantic light class separated from backing resource shape, optional GL timer-query diagnostics, rejected-caster reason breakdowns, off-screen shadow-map casters submitted independently from visible receiver scissors, projected CSM bias reports per-cascade near/far ranges, receiver bias uses texel-aware terms with scalar bias as a compatibility floor, hashed alpha uses stable world-space seeds, new debug/tuning cvars expose compare sampling, filter tap budgets, update budgeting, and a bias heatmap, and multiplayer maps such as `mp/q4dm1` keep their authored point lighting/shadows by using the legacy stencil path for point lights unless the experimental point-shadow-map path is explicitly enabled.
- [x] Projected shadow maps now store Quake 4 light-falloff depth directly instead of comparing projectively divided falloff depth, fixing detached or unraveled shadows from angled projected lights and letting the default projected bias and caster offset values drop to contact-friendly levels; GLSL cutout shadow casters also preserve material alphaFunc comparison modes instead of assuming all alpha-tested stages use greater-than thresholds.
- [x] Shadow-map static-cache signatures now cover caster materials, alpha/hash settings, caster offset, point-light range/depth mode, and view-fitted CSM inputs, while point/translucent caster shaders reject out-of-range depth and translucent coverage masks preserve material alphaFunc comparison modes.
- [x] Static models with authored collision helper surfaces now keep those surfaces available for traces without letting them cast duplicate stencil/prelight shadows, fixing stock crate-style props whose hidden collision hulls overlap the visible mesh.
- [x] GL renderer modernization groundwork is in place without replacing the shipping ARB2 path: SDL3 now negotiates explicit OpenGL tiers, `gfxInfo` reports selected tier/caps, `r_glTier`, `r_glDebugContext`, and `r_rendererMetrics` expose bring-up controls, extension parsing is token-safe, and the first render-graph, scene-packet, upload-manager, and metrics scaffolds wrap the legacy renderer for future GL 3.3/4.x work.
- [x] The GL renderer upload bridge now owns frame-temporary vertex-cache streaming behind the existing ARB2 renderer: dynamic uploads use persistent mapped buffers on capable GL 4.4+ drivers, map-range/subdata fallbacks below that, multiple frame buffers with sync when available, `r_rendererUploadMegs`/`r_rendererUploadPersistent` controls, `gfxInfo` reporting, and upload high-water/overflow metrics while preserving the old temp-buffer fallback.
- [x] The renderer upload layer now owns long-lived vertex-cache buffer uploads instead of only frame-temp streaming: `idVertexCache::Alloc` routes static vertex/index VBO creation and data upload through `UploadManager`/`BufferAllocator`, frees those buffers through the same owner, retains the old direct GL/virtual-memory fallback, expands `rendererUploadSelfTest`, and reports static upload/live-buffer counts in `gfxInfo` and `r_rendererMetrics`.
- [x] Renderer metrics now include nonblocking GL timer-query sampling behind `r_rendererGpuTimers`: the existing ARB2 backend reports resolved GPU timing for 3D views, 2D/GUI views, render-target work, copy-render work, special effects, and buffer switches when supported, `gfxInfo` exposes timer availability, and `rendererGpuTimerSelfTest` verifies the live query path without changing rendering output.
- [x] The renderer scene-packet contract now converts legacy draw data into `ScenePacket`, `PassPacket`, and `DrawPacket` records for validation and metrics: `r_rendererMetrics` reports scene/pass/draw packet counts, `gfxInfo` exposes the packet limits, and `rendererScenePacketSelfTest` verifies the packet contract while the legacy renderer still executes the original commands.
- [x] The render graph now consumes scene packets as its first packet-driven data source: legacy pass nodes are built from packet categories, graph pass/pass-packet/draw coverage appears in `r_rendererMetrics`, `gfxInfo` reports graph bridge limits, and `rendererRenderGraphSelfTest` validates world and GUI graph construction while ARB2 output remains unchanged.
- [x] Draw packets now carry legacy material/resource and sort metadata for future modern GL executors: packet records include material-resource entries, first bump/diffuse/specular stage images where available, permutation hints, legacy sort keys, geometry/scissor/cache availability, `r_rendererMetrics` reports material/resource/geometry packet coverage, and `gfxInfo` reports the material-record bridge limit without changing ARB2 output.
- [x] The first modern GL executor entry point is available behind `r_rendererModernExecutor`: capable GL 3.3+ tiers allocate a starter VAO and frame-constants UBO, consume scene packets/render-graph data each backend frame, report preparation coverage in renderer metrics and `gfxInfo`, and deliberately fall back to the ARB2 bridge for all drawing until modern draw submission lands.
- [x] The modern GL executor now validates a pass-oriented internal shader-library contract without changing visible rendering: capable GL 3.3+ tiers compile and reflect built-in depth, shadow-depth, flat-material, light-grid, fog/blend, GUI, and post-copy GLSL 330/410/430/450 variants as supported by the selected context, attach pass/material/permutation metadata plus uniform/sampler reflection, expose shader readiness in `gfxInfo` and renderer metrics, and keep ARB2 as the visible draw path.
- [x] The modern GL executor now builds its first prepare-only draw plan from scene packets and the packet render graph: capable tiers classify depth, shadow-depth, flat-material, light-grid, and fog/blend packet categories, select validated internal GLSL programs with permutation/reflection metadata, report plan draw/state-batch/fallback coverage in `gfxInfo` and renderer metrics, and still leave all visible drawing on the ARB2 compatibility path.
- [x] The modern GL executor now derives its first non-rendering submit-readiness plan from the draw plan: capable tiers identify which planned draws already have VBO-backed ambient/index cache state for a future GL 3.3 submitter, report missing-buffer fallback reasons plus program/vertex/index/scissor/material batch counts in `gfxInfo` and renderer metrics, and still leave all visible drawing on the ARB2 compatibility path.
- [x] The modern GL executor now has its first real opt-in GL 3.3 submission path behind `r_rendererModernSubmit`: ready draw-plan commands bind the internal GLSL programs, frame UBO, per-draw MVP, ambient VBO, and VBO-backed or uploaded CPU index data, execute masked diagnostic `glDraw*` calls for RenderDoc/metrics validation, report submitted/fallback/upload counts in `gfxInfo` and `r_rendererMetrics`, and still leave ARB2 responsible for the visible frame.
- [x] The modern GL executor now routes diagnostic submission through a shared `GLStateCache`: redundant program, VAO, buffer, framebuffer, viewport/scissor, blend/depth/stencil/raster, texture, sampler, and color-mask changes are suppressed and counted, ARB2 handoff invalidation is explicit, KHR_debug scopes/object labels are used where available, and `rendererGLStateCacheSelfTest`, `gfxInfo`, and detailed renderer metrics report the cache behavior without changing visible output.
- [x] GL 4.3+ and GL 4.5+ renderer tiers now execute real tier-gated work instead of only reporting capability labels: `GpuDrivenGL43` allocates and updates SSBO scene records, validation SSBOs, draw-indirect command buffers, and compute validation dispatches from the modern draw/submit plans, while `LowOverheadGL45` uses DSA buffer updates and multi-bind UBO/SSBO binding; `gfxInfo`, detailed renderer metrics, and the modern executor self-test report the active resource path while ARB2 remains the visible renderer.
- [x] The GL 4.5 low-overhead renderer path now covers the resource, binding, and upload primitives needed for CPU-cost reduction: graph textures/FBOs allocate through DSA immutable storage and named framebuffers, the modern executor creates a DSA sampler and uses buffer/texture/sampler multi-bind groups, submit-plan program/material batch compaction is reported, persistent upload streams default through the selected low-overhead tier with fence retire/wait diagnostics, and `rendererLowOverheadSelfTest` plus the safe validation matrix cover the path while bindless remains a default-off diagnostic experiment.
- [x] The GL context ladder is now shared across platform backends instead of being a Windows/SDL-local array: Windows SDL3, Linux SDL3, native Linux/GLX, and native macOS/NSOpenGL consume the same tier candidate table, forced modern tiers start at their requested version and walk downward through core and compatibility fallbacks, debug-context requests fall back to non-debug candidates, macOS caps requests at 4.1 core, and `rendererContextLadderSelfTest` plus `gfxInfo` cover the selected request.
- [x] The renderer validation matrix now has an automated safe runner and documented manual sign-off coverage: `tools/tests/renderer_validation_matrix.py` starts the staged client without map loads, runs all renderer foundation self-tests, probes forced `r_glTier` values, debug context fallback, and presentation cvars, emits Markdown/JSON reports under `.tmp/renderer-validation/`, and `docs/dev/renderer-validation-matrix.md` tracks the required SP/MP gameplay matrix separately until map startup is safe to automate.
- [x] The renderer now has an opt-in gameplay benchmark harness for target-hardware evidence: `tools/tests/renderer_gameplay_benchmark.py` launches from `.install`, enters the required SP maps or an MP listen server plus loopback client, waits for active gameplay through a one-shot SP/MP `g_autoExecAfterMapLoad` hook, captures screenshots, prints `rendererBenchmarkCapture`, `framePacingSnapshot`, and `gfxInfo`, optionally compares deterministic TGA references, and fails cases on renderer overflow/idStr/shader/link/fatal log markers.
- [x] Renderer recovery validation now targets high-FPS gameplay instead of low-cap smoke tests: `com_maxfps` defaults to `240`, presentation coverage uses `120/240/uncapped` instead of `30 FPS`, `game/storage1` is the primary smoke scene, pacing-only runs avoid diagnostic overhead, wall-clock sampling measures real three-second gameplay windows, and parsed frame-pacing thresholds can enforce a `120+ FPS` acceptance window.
- [x] Stock Quake 4 lighting and default renderer performance are restored in dense indoor scenes: authored detail lights now remain enabled by default, portal-sky setup once again uses retail-style PVS/skybox checks, unused shadow-map caster work is skipped when shadow maps are off, expensive shadow-surface scissor tightening is opt-in on the default stencil-shadow path, and BSE effects keep conservative default servicing while the more aggressive loop-service/frustum culls remain opt-in diagnostics.
- [x] BSE particles now have an opt-in soft-particle path: `r_softParticles 1` depth-fades compatible blended and additive sprite/oriented effect quads against the opaque scene depth, with `r_softParticleFadeDistance` controlling the fade width, while decals, beams, electricity, trails, model debris, custom GLSL stages, post-process materials, and non-effect translucent materials remain on their normal renderer path.
- [x] Soft particles now survive the modern visible renderer handoff safely: when `r_softParticles 1` is active, only BSE particle quads with active compatible material stages are replayed through the legacy soft-particle shader after modern scene/depth composition, avoiding the broken-effect failures caused by routing every BSE surface through the particle shader.
- [x] Stock character lighting parity is protected while the new GLSL interaction paths mature: enhanced material interactions and shadow-map receiver sampling now only claim eligible static stock geometry, while MD5/MD5R skinned, deformed, packed, or dynamic character surfaces retain the proven ARB2/stencil interaction path to avoid mirrored body seams and broken eye shading.
- [x] Shadow-map receiver lighting now preserves stock material decoding unless enhanced materials are explicitly enabled: the projected and point shadow interaction shaders use the legacy normal/specular path by default and only apply the newer normal-scale/specular-boost/fresnel math under `r_enhancedMaterials`, restoring character eye and body lighting parity while retaining the opt-in enhanced material path.
- [x] Modern material lighting now uses the same Quake 4 bump-map channel contract as the classic interaction path and carries `r_enhancedMaterials` normal scale, specular boost, and fresnel controls through both uniform and draw-record submission, preventing the modern G-buffer, deferred, and forward+ paths from mis-decoding stock materials when enhanced materials are enabled.
- [x] Modern GL submit readiness now recognizes frame-temporary/generated index-cache blocks as real element-buffer sources: deformed surfaces, packed MD5R cache materialization, packed light interactions, and GLSL-prepared interaction paths now tag temp index allocations correctly, preserving ARB2 behavior while reducing false submit fallbacks and unnecessary CPU-index upload pressure.
- [x] Scene packets now originate from front-end view producers instead of being rebuilt from the backend command stream on normal frames: completed `RenderWorld` views emit packets after culling/subview generation/sorting, GUI fullscreen views emit their own packets, `r_rendererMetrics` reports whether packets came from `frontend` or the backend fallback, and `rendererScenePacketSelfTest` verifies both paths while ARB2 remains the visible renderer.
- [x] Renderer scene packets now describe real pass work instead of multiplying every visible surface across every pass: depth, ambient/material, GUI, post, light-grid, interaction, fog/blend, stencil-shadow, and shadow-map packets are filtered from the same inputs the legacy renderer actually consumes, reducing modern renderer preparation work and keeping lighting/shadow ownership data aligned with ARB2 fallback behavior.
- [x] Modern renderer pass ownership now fails closed on incomplete visual parity: material/geometry fallbacks, interaction lighting, fog/blend, light-grid contribution, and shadow receiver gaps block modern visible replacement before expensive passes are planned, ARB2 lighting/shadows stay visible instead of being dropped, and renderer metrics report the first ownership blocker plus duplicate/drop accounting.
- [x] The renderer graph now models resources instead of only counting passes: ordered packet-pass nodes carry virtual read/write/clear/resolve/present edges for scene color/depth, post targets, imported light-grid data, and the back buffer; resource lifetimes plus transient/imported/aliasable counts appear in `r_rendererMetrics`, `gfxInfo` reports the graph limits, and `rendererRenderGraphSelfTest` verifies world and GUI resource graphs while ARB2 still owns visible rendering.
- [x] The render graph now owns real GL resource handles without changing visible output: `RenderGraphResources` imports the back buffer, legacy scene color/depth, and light-grid data, allocates transient scene/post textures with complete FBOs, tracks lifetimes/alias groups/physical allocation ids, adds invalidate metadata to graph metrics, and exposes `rendererRenderGraphResourceSelfTest` plus `rendererRenderGraphResourceDump` while ARB2 remains the visible renderer.
- [x] The modern renderer now has a stable material resource table without changing visible output: packet material records are expanded into per-frame material ids with material class, blend/sort/alpha-test metadata, texture/sampler descriptors, shader-register ranges, GL 4.3+ descriptor placeholders, disabled bindless fields, explicit fallback reasons, `materialTable(...)` metrics, `rendererMaterialResourceTableSelfTest`, and `rendererMaterialResourceTableDump` while ARB2 remains the visible renderer.
- [x] The modern renderer now carries backend-neutral geometry and instance packet records without changing visible output: scene packets copy vertex/index cache handles, offsets, formats, bounds, skinning/deform/upload classifications, matrices, entity color, shader-register ranges, visibility categories, overflow causes, and sort-key validation into records that modern draw/submit plans can consume while ARB2 remains the visible renderer.
- [x] The modern renderer now has a Shader Library V2 foundation for visible pass bring-up without changing output: capable GL tiers compile controlled depth, shadow-depth, G-buffer, deferred resolve, clustered forward, transparent, GUI, post, and debug GLSL families with compact permutation keys, reflected UBO/SSBO/sampler/image/attribute records, pass/material/tier compile diagnostics, `gfxInfo` coverage, `rendererShaderLibrarySelfTest`, and a fail-closed debug reload gate behind `r_rendererShaderReload` while ARB2 remains the visible renderer.
- [x] The modern renderer now has its first opt-in visible-depth bridge for renderer bring-up: `r_rendererModernVisibleDepth` writes graph-backed scene depth and compatible shadow-depth resources while ARB2 still draws the final color frame, `r_rendererModernDepthDebug` can overlay those depth resources for inspection, unsupported materials and stencil-shadow cases fall back cleanly, and `rendererVisiblePathSelfTest` plus the safe validation matrix cover the path.
- [x] The modern renderer now has an opt-in opaque G-buffer bridge without replacing the visible ARB2 frame: `r_rendererModernOpaque` writes graph-backed albedo, normal, material/specular surrogate, and emissive/light-grid attachments through a GL 3.3 MRT path, `r_rendererModernGBufferDebug` can overlay each attachment for inspection, unsupported materials/geometry fall back cleanly, and `rendererGBufferSelfTest` plus the safe validation matrix cover resource, shader, packing, fallback, and bandwidth reporting.
- [x] The modern renderer now prepares conservative clustered-light data for the hybrid lighting path without changing the visible ARB2 frame: packet-scene `viewLight_t` lists are classified into point, projected, fog, ambient, and special light records, binned into 8x6x16 cluster grids with overflow accounting, uploaded through GL 3.3 UBO fallback buffers, reported through `gfxInfo`/renderer metrics, visualized with `r_rendererClusterDebug`, and covered by `rendererClusterGridSelfTest` plus the safe validation matrix.
- [x] The modern renderer now has its first opt-in deferred-lite resolve bridge: `r_rendererModernDeferred` consumes graph-backed G-buffer/depth resources and clustered-light UBOs, writes a graph-owned `deferredLight` target, accumulates baseline point/projected and light-grid contribution, reports fallback pressure for unsupported fog/special/overflow cases, exposes `r_rendererModernDeferredDebug` overlays, and is covered by `rendererDeferredResolveSelfTest` plus the safe validation matrix while ARB2 remains the visible renderer.
- [x] The modern renderer now has an opt-in clustered forward+ bridge for material classes outside the deferred subset: `r_rendererForwardPlus` routes eligible opaque, alpha-tested, and transparent/fog-blend draws through graph-backed `sceneColor`/`sceneDepth` resources and clustered-light UBOs, preserves transparent sort ordering, reports forward draw, fallback, overdraw, light-read, and GPU-timer metrics through `gfxInfo`/`r_rendererMetrics`, and is covered by `rendererForwardPlusSelfTest` plus the safe validation matrix while ARB2 remains the visible renderer.
- [x] The modern renderer now has its first guarded visible-frame bridge behind `r_rendererModernVisible`: the option requests modern depth, G-buffer, deferred-lite, forward+, and present ownership together, composes `deferredLight` plus `sceneColor` to the back buffer when the frame has no legacy-owned GUI/post/special/subview islands, records owner/resource fallback reasons otherwise, and is covered by `rendererModernVisibleSelfTest` plus the safe validation matrix while ARB2 remains the default visible renderer.
- [x] The modern visible bridge now has Phase 14 compatibility ownership instead of unknown command islands: every render-pass category is assigned a modern owner or explicit compatibility fallback, fullscreen GUI packets get a dedicated modern draw pipeline and overlay replay after composition, light-grid passes are modern-owned by the hybrid path, post/copy/subview/render-demo/BSE categories report graph and fallback buckets, render-demo fallback remains deterministic, and `rendererModernCompatibilitySelfTest` plus the safe validation matrix cover the inventory.
- [x] Modern visible rendering now preserves retail feedback and subview effects without forcing a full-frame fallback: heat-haze/current-render materials draw through a narrow legacy feedback lane over the composed modern scene, `_currentRender` is refreshed at the right moment for fire and distortion effects, and generated subview/render-demo views keep their legacy sidecar passes while the main scene stays on the modern handoff.
- [x] Modern renderer shadow-map gating now keeps deferred-lite and forward+ diagnostic lighting alive when shadow-map receiver sampling is unavailable, records those blocked receivers separately from real stencil fallbacks, ignores intentional no-shadow/no-receiver skips for ownership, and keeps full visible replacement fail-closed so legacy shadows remain visible until modern receiver sampling reaches parity.
- [x] Optimized prelight shadows now sanitize legacy invalid prelight-model sentinels before view shadow submission, preventing airdefense-style access violations while falling back to dynamic/static shadow handling when no real precomputed model exists.
- [x] The modern renderer now has Phase 15 compatibility gates before any default-promotion decision: `gfxInfo` reports driver quirks and fallback gates, startup masks known-bad driver features before tier selection, `rendererCompatibilityGatesSelfTest` covers missing UBO, broken MRT, missing timer query, missing buffer storage, rejected debug context, and synthetic quirk downgrades, and the validation matrix now includes deterministic capture, RenderDoc tier, long-run, SP, and MP sign-off checklists.
- [x] The modern renderer now has a Phase 16 benchmark and scalability contract: renderer metrics feed `rendererBenchmarkCapture` with CPU front-end/visibility/packet/graph/submit/present timings, GPU pass samples, upload/draw/light/cluster/fallback counts, rolling P50/P95/P99 frame-time percentiles, low/baseline/modern/high-end budget presets, threshold override cvars, and an opt-in adaptive cluster-grid experiment, with `rendererBenchmarkSelfTest` plus validation-report thresholds covering the capture format before performance claims are made.
- [x] The modern renderer now has a Phase 17 default-promotion gate instead of an unconditional renderer flip: `r_rendererModernAutoPromote` remains off by default, `gfxInfo` reports the promotion reason, `rendererDefaultPromotionSelfTest` covers unsigned eligibility, signed promotion, explicit `r_renderer arb2` escape, forced `r_glTier legacy`, compatibility-gate blocks, and missing ARB2 rollback, and the validation docs now require SP/MP gameplay, RenderDoc, benchmark, and rollback sign-off before auto promotion is used.
- [x] The renderer recovery plan now has a conservative-default safety gate for Phase 13: `gfxInfo` reports `Renderer default safety`, `rendererDefaultSafetySelfTest` verifies that ARB2 remains the visible clean-start or explicit rollback path with modern executor/submit/visible/debug/validation/bindless/shader-reload side paths off by default, and the README plus renderer docs list the rollback and quarantine cvars for users testing modern renderer bring-up.
- [x] The OpenGL tier ladder now reports an explicit workload contract for every selected tier: `gfxInfo` shows selected/requested readiness, forced-tier degradation, fail-closed fallback state, ARB2 rollback availability, GL3.3 CPU workload readiness, GL4.3 GPU-driven readiness, GL4.5 low-overhead readiness, GL4.6 top-tier readiness, and concise missing reasons, with `rendererTierContractSelfTest` and the safe validation matrix covering the contract.
- [x] The GL 4.3 GPU-driven path now performs real compute work instead of only validating resource creation: submit records are uploaded to SSBOs, compute culls visible/scissor-rejected commands, validates clustered-bin counts against a CPU reference, generates compact draw-indirect commands for a compatible indexed batch, can execute the range through masked multi-draw indirect under `r_rendererGpuValidation`, and reports dispatch/readback/mismatch/indirect-submit metrics through `gfxInfo`, `r_rendererMetrics`, and `rendererGpuDrivenSelfTest`.
- [x] The modern renderer recovery path now keeps blocked visible-frame requests cheap: compatibility ownership is analyzed before expensive modern passes run, blocked frames stay analyze-only unless an explicit sidecar/debug request is active, shadow-map diagnostics use the same producer gate, `modernPassGate` explains skipped work, and renderer debug/status strings no longer flood validation logs with `idStr::snPrintf` overflow warnings.
- [x] Modern clustered lighting now carries real point/projected light data instead of placeholder vectors: per-light descriptors include origin, radius, evaluated color, falloff/projection planes, image/sampler handles, depth/scissor policy, and shadow fallback state; deferred and forward+ lookup clusters in view-depth space, use real attenuation/specular math, upload all per-view grids through UBO/SSBO paths with DSA/multi-bind support, and fail closed when cluster spill or capacity limits would otherwise drop light contribution.
- [x] The modern renderer now has a fail-closed pass-ownership handoff for opt-in visible replacement: modern-owned depth, lighting, light-grid, ambient/material, fog, GUI, and shadow/stencil responsibilities can suppress their exact ARB2 counterparts only after modern resources and fallbacks pass validation, while mixed diagnostics keep ARB2 active and `rendererPassOwnershipSelfTest` verifies duplicate-ownership prevention.
- [x] Modern renderer geometry correctness advanced: promoted modern draws now bind the full `idDrawVert` layout, carry per-draw model-view matrices into G-buffer/forward+ shaders, reconstruct view-space normals and tangent handedness for mirrored UVs/transforms, validate the layout through renderer self-tests, and keep deformed/GPU-palette-skinned surfaces on the legacy path until their contracts are safe.
- [x] Modern renderer material correctness advanced: promoted G-buffer and forward+ draws now use the material table's stage contract, legacy alpha-test thresholds, normal/specular/emissive textures, and conservative visible/shadow fallback gates instead of placeholder normals and first-texture-only sampling.
- [x] Modern renderer material/geometry promotion is now stricter and closer to retail behavior: unsupported material features and geometry producers fall into stable fallback buckets, material cull/two-sided state, mirrored or negative transforms, material color registers, transparent blend families, and weapon/model depth hacks are carried into modern submissions, and the opaque G-buffer path preserves reusable scene depth instead of clearing away a valid prepass.
- [x] Modern clustered lighting now scales beyond the first tiny prototype: cluster grids are budgeted from viewport/light pressure, GL 3.3 keeps a UBO-safe CPU-binned path, GL 4.3+ can upload larger SSBO-backed light/index lists, and dense clusters report spill pressure instead of turning the fifth light into a hard overflow.
- [x] Modern shadow mapping is now part of the shared renderer plan instead of a separate side path: the renderer builds a per-frame shadow plan from visible lights, records mapped/stencil-fallback/skipped decisions, budgets shadow candidates against the active renderer preset, names projected/point/cascade/translucent shadow resources in the render graph, and feeds shadow descriptor state into clustered light records for deferred and forward+ follow-up while preserving the existing `r_shadows`/`r_useShadowMap` fallback contract.
- [x] Shadow-map validation and modern receiver safety advanced again: mapped shadow descriptors now carry atlas tile, matrix, cascade, bias/filter, scissor, and readiness metadata, clustered deferred/forward+ fail closed to stencil fallback when modern receiver sampling is unavailable, and new debug modes can hard-disable receiver bias, PCF, caster polygon offset, or receiver-plane/normal bias during gameplay.
- [x] Shadow-map regression coverage now has a compact gameplay profile for projected, point, cascaded, character, and alpha-tested captures, with screenshot hashes or optional TGA reference comparisons recorded in reports and safe planner self-tests checking the same coverage tokens before release.
- [x] Modern deferred/forward+ shadow handoff now uploads real per-light shadow descriptor buffers with map type, compare/bias model, atlas tile rectangles, cascade split/bias data, and fallback/readiness flags; diagnostics and self-tests report descriptor upload readiness while visible modern shadow ownership stays fail-closed whenever required sampler resources are missing.
- [x] Modern deferred/forward+ shadow setup now binds the actual ARB2-owned projected and point shadow atlases plus translucent moment textures on fixed modern sampler units, with diagnostics separating binding-contract readiness from real texture availability for the modern lookup shaders.
- [x] Shadow-map lighting no longer throws away an entire mapped light when one receiver surface is incompatible with the GLSL receiver path: eligible static receivers keep mapped shadows while skinned, generated, custom, or dynamic receivers use the stencil fallback for that light.
- [x] Gameplay-authored flashlights, weapon muzzle flashes, vehicle lights, and Strogg hover headlights now validate their projected-light vectors before registration, preserving valid stock cones while safely demoting malformed projector definitions to point lights instead of feeding broken frustums into shadow mapping.
- [x] The modern hybrid visible path now has a graph-owned composition handoff: alpha-tested depth and shadow-depth draws can use material alpha thresholds safely, CPU-skinned depth is accepted while unsafe skinning/deforms remain legacy-owned, deferred and forward+ shaders consume the shared shadow descriptor policy, unresolved stencil/shadow fallbacks block visible replacement fail-closed, and `hybridSceneColor` receives the combined deferred/forward output before the final back-buffer copy.
- [x] The modern hybrid visible path now feeds the existing HDR and post stack instead of bypassing it: supported modern tiers allocate FP16 scene/post composition targets, the combined deferred/forward scene is copied into the active HDR scene target before SSAO, motion blur, bloom, tone mapping, and authored `_currentRender` passes, graph depth is handed to depth-aware post effects, auto exposure is gated to completed modern handoff frames, and GUI replay remains a final LDR overlay.
- [x] The modern renderer now has a stronger conservative visibility and occlusion foundation for detailed-scene performance: submit commands cache projected bounds/depth/safety visibility packets, `r_rendererOcclusion` and `r_rendererHiZ` gate scissor/frustum/screen rejection and Hi-Z candidate accounting, graph-owned Hi-Z pyramids build through an explicit max-depth reduction path without in-frame GL state queries, and shadow-caster safety metrics preserve Quake 4 area/portal/PVS culling as the first tier.
- [x] The modern renderer now selects only the passes a frame actually needs: G-buffer, deferred, and forward+ work are derived from ready submit commands and explicit debug requests, empty passes are skipped, single-source modern visible composition remains valid, repeated G-buffer/forward+ FBO layouts are cached, and `gfxInfo` reports pipeline stability, batching, validation-readback policy, and GL 3.3/4.3/4.5 fit.
- [x] Modern renderer draw submission now avoids rebuilding the fixed `idDrawVert` vertex-input layout for every draw: GL 4.3+ tiers keep the attribute format on the modern VAO and switch only the vertex source binding, while the GL 3.3 path caches repeated VBO/stride/base-offset layouts; `gfxInfo` and renderer self-tests report the vertex-input cache path.
- [x] Modern renderer submit plans now bucket reorder-safe world opaque/depth work before execution: same-view depth, G-buffer, and forward+ opaque commands are stably sorted by pipeline/program/material/geometry/scissor state while transparent, GUI, post, viewmodel, subview, and shadow work keeps source ordering; `gfxInfo`, renderer metrics, and self-tests report eligible draws, moved draws, bucket counts, and saved state batches.
- [x] The GL 4.3 GPU-driven submit path now feeds multi-draw indirect from a real per-draw record stream instead of one first-seen signature: draw records carry MVP/model-view matrices, material ids, local parameters, alpha refs, and material flags in an SSBO, an instanced record-index attribute selects the right record for each indirect command, and the executor emits one `glMultiDrawElementsIndirect` call per compatible bucket while `gfxInfo`, renderer metrics, and GPU-driven self-tests report draw-record bytes, bucket counts, bucketed commands, and indirect fallbacks.
- [x] The GL 4.3 GPU-driven submit path now consumes the graph-owned Hi-Z pyramid for conservative indirect culling: modern-submit frames build a depth prepass when Hi-Z culling is enabled, compute tests eligible opaque/material draw bounds against the max-depth pyramid, compacts visible indirect commands per bucket with zero-count padding for GL 4.3-safe MDI submission, and reports bucket-buffer readiness, compacted commands, Hi-Z candidates, and validation-only rejection counts in `gfxInfo` and renderer metrics.
- [x] Conservative BSE visibility culling now avoids cached-bounds pop-ins and queued-surface flicker by default: offscreen loop service deferral stays limited to portal/PVS area culling, stale portal-culled loops get budgeted state/bounds refreshes through `bse_culledLoopServiceMaxMsec` and `bse_culledLoopServiceRefreshBudget`, frustum rejection runs after the effect model is rebuilt from current geometry, queued BSE surfaces submit frame-local vertex/index snapshots so later view rebuilds cannot clear their caches, and the older cached-bounds frustum path remains available behind `bse_useCachedFrustumCull` for opt-in profiling.
- [x] Modern clustered lighting now uses compact CSR-style cluster lists instead of fixed per-cluster slots: each per-view grid uploads `(offset,count)` headers plus a flat packed light-index stream, dense clusters stay lossless within the shared budget on GL 3.3, and GL 4.3+ can rebuild the same headers/index stream with compute count/prefix/fill passes while `gfxInfo`, renderer metrics, and `rendererClusterGridSelfTest` report CSR and compute-binning readiness.
- [x] GL 4.3+ modern material draws can now collapse repeated classic texture binds through a conservative per-frame sampler table: the material resource table assigns table indices for loaded textures and fallback images, shaders keep the GL 3.3 classic sampler path but can select table entries with per-draw texture indices, and `gfxInfo`, renderer metrics, and low-overhead self-tests report table size, draw usage, uniform updates, and fallback pressure while bindless remains opt-in only.
- [x] Modern renderer pass handoff now avoids full GL teardown between modern passes: depth, Hi-Z, G-buffer, deferred, forward+, GUI, and diagnostic submits use a soft intra-modern restore that keeps the state cache warm, then perform one full restore before the ARB2 legacy handoff, with `gfxInfo`, renderer metrics, and visible-path self-tests reporting soft/full restore coverage.
- [x] Modern renderer transient uploads now use the shared upload-manager stream for frame constants and GL 4.3 GPU-driven scene, indirect, draw-record, bucket, and validation buffers when available; fixed-buffer fallbacks remain intact, upload ring rotation is configurable through `r_rendererUploadFrameBuffers`, opt-in GPU validation readbacks are deferred behind nonblocking sync polling, and launch-time validation now waits for real scene draw packets instead of doing GPU-driven work on loading/GUI frames.
- [x] Forced modern GL tiers now keep stock-world lighting on the visible ARB2 bridge by preferring versioned compatibility contexts before core-profile diagnostics; ARB2 initialization also rejects core-profile contexts explicitly instead of advertising a bridge that can only produce a black frame.
- [x] Modern renderer auto-promotion now requires explicit Phase 8 validation evidence, not just a boolean sign-off: `r_rendererPromotionEvidence` must carry the complete zero-warning visual/gameplay/RenderDoc/performance/presentation/rollback/debug-off token before `r_rendererModernAutoPromote 1` can activate, and `gfxInfo`, `rendererDefaultPromotionSelfTest`, `rendererDefaultSafetySelfTest`, and the validation matrix all report the same evidence gate.
- [x] Filter-style decal mips now fold coverage alpha into the neutral multiply color per generated level, so stock terrain stains such as `game/airdefense1` no longer reveal their decal bounds when the flashlight crosses them.
- [x] Decal rendering now applies after all direct and baked light-grid lighting, so bullet marks, scorch marks, and terrain stains modulate the fully lit scene consistently; `r_skipDecals` also suppresses ordinary decal-sort surfaces and the modern forward+ decal overlay, matching projected decal behavior.
- [x] Runtime logs now open under the selected game directory as soon as filesystem startup can write files, so `+set fs_game baseoq4 +set logFileName logs/openq4.log` writes to `fs_savepath/baseoq4/logs/` instead of being captured early under `q4base/logs/`.
- [x] Retail BSE trail-material parity is restored for shared bullet-impact effects: particle templates now retain the authored/default trail-material name, motion trails resolve that material at finish time instead of relying on an early static pointer, and the stock machinegun/shotgun impact spark trails once again use Quake 4's `gfx/effects/particles_shapes/motionblur` trail instead of falling back to the engine default texture.
- [x] Ragdoll startup now follows Quake 4's retail articulated-figure path again: stock corpses no longer pick up openQ4-only AF handoff velocity/depenetration heuristics or extra per-body angular-velocity injection during pose transfer, and the AF contact solver is back on retail's tighter contact budget and per-pair planar-contact cap for SP/MP ragdolls.
- [x] Animated MD5 reference bounds now use exact posed mesh extents when available, eliminating the fixed-screen-square scissor clipping that could chop up player heads/bodies and attached weapons in stock scenes such as the normal `game/airdefense2` launch path.
- [x] VS Code launch-task runtime parity is fixed on Windows: the client and dedicated entry path now preserves native Windows argv quoting, startup `+set` handling now preserves the full multi-word value tail for debugger/task launches that fail to quote space-containing arguments cleanly, and projected shadow-map defaults now carry enough receiver bias to stop stock flashlight scenes such as `game/airdefense2` from collapsing into near-black self-shadowing.
- [x] Decl reload progress now uses a shared retail-style tool print path: `Common.*` centralizes tool-routed print behavior, `reloadDecls` progress lines now use that helper instead of printing straight to the runtime console, and the decl browser can surface reload progress in its status bar while normal runtime sessions still fall back to the usual console/log output.
- [x] Decl late-load warning suppression now uses one shared retail-like any-tool helper: `Common.*` centralizes the "is any tool active?" check alongside the existing masked tool helpers, and decl late-load warnings now read through that path instead of open-coding focus and editor-bit state inside the decl manager.
- [x] Decl tool-state checks now share one retail-style helper: masked tool-flag queries live in `Common.*`, entityDef media caching uses that shared helper instead of a raw editor-bit test, and decl-validation state checks now read through the same path so tool-sensitive decl behavior stays centralized.
- [x] Articulated-figure `fastEval` text now matches retail style: the parser still accepts explicit boolean forms for tools, bare `fastEval` behaves like retail anywhere in the settings block, rebuilt AF text only emits `fastEval` when true, and default AF definitions stop writing `fastEval 0`.
- [x] Packed `.decls` writer policy is now explicit: openQ4 keeps its extended game-section export coverage by default, exact-retail game-type coverage is selectable through `com_singleDeclFileWriteMode` or `writeDeclFile retail`, and export logs now report which policy produced the file.
- [x] Effect decl allocation is now a hard integrated-BSE invariant: startup probes the `effect` allocator before registration, allocator failures fatal instead of producing plain `idDecl` stubs, and dedicated servers keep the disabled BSE runtime while still using the real BSE effect-decl parser.
- [x] Playback decl finalization now fires the retail-style playback-finished tool bridge after resampling recorded paths, giving editor/tool workflows a completion signal while remaining a no-op in normal runtime builds.
- [x] Articulated figure decl content masks now preserve Quake 4's `vehicleclip`, `flyclip`, and `itemclip` collision bits when parsing or resaving AF files, restoring retail collision-mask round-tripping for stock and SDK-derived articulated figures.
- [x] Light-grid baking now writes versioned v5 metadata with a deterministic bake-settings/layout hash, records probe and atlas layout stats, prints per-phase timing/counter summaries including async readback stalls, and rebuilds stale baked outputs automatically when the map layout or bake-affecting settings change.
- [x] Light-grid interpolation is now visibility-aware: bakes emit companion `area*_lightgrid_vis.tga` atlases with directional distance moments, and runtime sampling uses those moments with a self-shadow bias to reduce wall/corner light leaks.
- [x] Light-grid probe placement now detects solid/near-solid positions, relocates probes within a bounded search when possible, records richer validity states and relocated origins in v7 metadata, emits compact `area*_lightgrid_pos.tga` probe-position atlases for runtime interpolation, and reports relocated/near-solid probe counts in bake diagnostics.
- [x] First-person weapon/viewmodel surfaces now receive baked light-grid indirect diffuse through a dedicated weapon pass that samples the active view area's probes while keeping depth-hacked weapon geometry out of the main world receiver pass.
- [x] Light-grid area transitions now blend across visible, unblocked portal neighbors near doorway/window boundaries, using `r_lightGridPortalBlend` to smooth indirect-light seams without changing baked asset formats.
- [x] Light-grid atlas residency now keeps visible areas and unblocked portal neighbors hot while loose fallback atlases can still use an age-based window controlled by `r_lightGridResidencyFrames`, reducing reload churn and traversal hitches when moving through connected areas.
- [x] Light-grid indirect rendering now uses a material-compiled representative diffuse stage instead of redrawing every diffuse stage, reducing extra geometry submissions for multi-diffuse materials while retaining a fallback when the preferred stage is conditionally disabled.
- [x] Startup asset validation now ignores retail Quake 4 game-binary PK4 archives (`game000.pk4` through `game300.pk4`, plus `gamex*.pk4` variants), so openQ4 only requires and verifies the stock media PK4s it actually uses.
- [x] Retail decl loading parity improved: Quake 4 decl folders now honor retail recursion/unique-file behavior, no-cache decl lookups reach entityDef inheritance/media caching, legacy Doom 3 PDA/email/video/audio console commands are no longer exposed as supported decls, and playback/table/lip-sync/material-type decl parsing plus playback runtime sampling/record hooks now handle more shipped Raven asset data safely.
- [x] Packed decl-manager parity advanced: the retail single-decl-file API is enabled again across engine/game headers, packed `.decls` sections can be read and written through controlled cvars/commands, stored decl indices are preserved during load, validation/allocation/tool hooks are available, packed stub decls expand lazily from their original source text, PDA/email/video/audio decl families are included in the framework packed section, and the startup/session/network asset-log wiring now selects and refreshes per-map packed decl files when the retail cvars are enabled.
- [x] Packed decl asset-log wiring now follows the retail `assetlogs/<map>` naming contract, including entity-filter suffixes, while the decl manager preserves the real `maps/...` path when selecting per-map `.decls` files.
- [x] Regenerated collision caches now keep the first map entity named as the world model even after openQ4's trace-model slots are initialized, preventing MP map loads from null-dereferencing the clip world when a stale stock `.cm` has to be rebuilt.
- [x] Material decl parity advanced: per-level material use counts now resolve onto image diagnostics like retail, Quake 4 material tokens for `alphaFunc`, `fragmentParm`, `sky`, and `needCurrentRender` now preserve their authored behavior instead of being ignored or defaulting to Doom 3-only semantics, `sort gui` and retail sort constants now resolve consistently, and unknown `materialType` references now warn like the shipped engine.
- [x] Sound shader decl parity improved: descriptions, min/max `frequencyshift`, `frequentlyUsed`, `no_shakes`, `minSamples`, and +10 dB `volumeDb` clamping now survive parsing, sound shader memory/list diagnostics include their retained decl state, level-load reference marking now propagates from sound decls to their loaded samples, retail entry-only default/duration queries are restored, and the retail `rvSoundShaderEdit` bridge now exposes sample/shake/purge/load helpers for tools.
- [x] Effect/player-model/skin decl parity advanced: skin and player-model decl diagnostics now report retained allocations and useful player-model fields, BSE effect decls now expose retail-style segment lookup/copy/editor-original helpers with safe bounds handling, and the integrated BSE path now wires the retail `rvDeclEffectEdit` bridge instead of leaving it null.
- [x] Decl editor/tool wiring advanced: AF, playback, and lip-sync decl edit adapters are now available engine-side, lip-sync decls can rebuild retail-style text, Raven decl parsers now expose the no-caching signatures expected by the shared SDK headers, player-model parsing performs the retail media-cache pass, and table/material-type parsing now follows recovered retail edge-case behavior.
- [x] Retail decl validation parity advanced: table, skin, entityDef, AF, material-type, lip-sync, playback, and player-model validation now performs the retail temporary-decl parse/default-state check instead of accepting every text block, and the base `idDecl` no-caching parse shim now preserves the caller's cache-suppression request.
- [x] Added a default-off `r_motionBlur` graphics option for subtle camera motion blur, including depth-aware sampling, safe history resets on map/view changes, and a debug vector view for tuning.
- [x] Added optional rigid-object motion vectors behind `r_motionBlurObjectVectors`, improving blur on moving doors, platforms, and other non-skinned opaque objects while preserving the camera-only fallback for unsupported surfaces.
- [x] Packed MD5R light interactions now advance along the retail ARB2 path without regressing the stable hybrid renderer: packed interaction passes switch to the stock `md5rinteraction*` vertex-program family, upload the retail MD5R interaction parameter block in `program.env[75..91]`, interaction `lightTris` now emit the retail per-prim-batch headered index stream instead of staying on the older flat filtered list when the packed builder succeeds, packed interaction rendering consumes that headered batch stream directly while continuing to fall back to the classic interaction path if a packed program/buffer bind or retail packed light-tri build is unavailable, retail-style packed stage texturing now picks the correct MD5R program variant and submits full ambient/material stage batches directly from the packed draw buffers when the stage path is fully prepared, ARB ambient material-program stages with an authored `md5r*.vp` alias now use the same prepared packed draw/runtime path instead of forcing the classic ambient-cache vertex layout, packed late blend-light passes now explicitly refresh the frame-temp ambient/index view they need instead of depending on earlier light submission to have populated it, the deferred light-grid indirect pass now does the same so packed surfaces stop assuming a prior ambient submission already materialized classic cache-backed draw data, the shared depth / ambient shader passes now run through the same packed cache-prep helper before they touch classic `ambientCache` state, the generic `RB_RenderTriangleSurface` / `RB_T_RenderTriangleSurface` path now gives packed surfaces the same frame-temp classic draw view instead of dropping them straight to immediate-mode fallback, the last-resort `RB_DrawElementsImmediate` path can now materialize packed prim-batch triangles on demand instead of assuming classic `verts/indexes` residency, the GLSL material/projected-shadow/point-shadow interaction creators now recover packed ambient-cache state from the source ambient surface instead of assuming earlier interaction submission already populated classic draw-vertex data, the draw core now recognizes a second prepared packed direct-draw context so the MD5R depth and fog passes can route their per-prim-batch submission through `RB_DrawElementsWithCounters()` instead of each carrying a separate private loop, those ARB2 packed depth/fog entry points now attempt direct MD5R submission before falling back to the older classic-cache requirement, and renderer diagnostics such as `r_showLightCount`, `r_showNormals`, `r_showEdges`, and the other raw-geometry overlays now rematerialize packed interaction/ambient data instead of silently skipping MD5R-backed surfaces.
- [x] Fixed the active light-parity fog regression in `game/storage1` and other stock scenes: fog passes now force the fog light's current color instead of inheriting stale per-vertex color arrays, the generated `_fog` / `_fogEnter` lookup textures once again stay full RGBA instead of collapsing to alpha-only classification, and packed MD5R fog again routes through the retail `md5rbasicfog*` variant/palette path for skinned packed surfaces.
- [x] Storage 1's main shaft beam now follows retail fog/material ordering: opaque, decal, and `sort far` materials draw before fog while medium/near translucent BSE beam layers draw afterward, restoring the bright white core and blue additive shaft instead of letting fog wash over the blended beam. HDR tonemapping now preserves stock LDR-authored highlights until the near-white shoulder, keeping additive beam/fx colors vivid while still compressing true scene-referred HDR energy. DXT-compressed GUI/image strips with heights smaller than one 4x4 block also decode through a block-aligned scratch buffer, preventing debug-build startup crashes while loading stock DDS replacements.
- [x] Collision-model parity improved for generated/runtime CM data: retail Quake 4 primitive tracking and polygon texture-basis data are now preserved during CM generation/serialization, render-model collision no longer over-merges across distinct source surfaces, and `.proc` CRC validation now matches retail CM load behavior more closely.
- [x] Collision-model load parity improved for stock maps and SP entities: CM parsing now reuses existing model slots instead of exhausting submodel capacity, world/map submodel naming matches retail expectations more closely, retail-style SP clipmodel fallback no longer probes entity names, and stock `game/medlabs` map init no longer emits `contains different model`/`no free slots` CM failures.
- [x] Collision-model lifetime parity now more closely matches retail Quake 4: map teardown drops CM reference counts instead of clearing the entire manager, purges free model geometry in place for slot reuse, and SP/MP map shutdown now purges unreferenced CM data before the next level load, fixing slot exhaustion during SP transitions such as `game/mcc_1` to `game/convoy1`.
- [x] MCC med-bed intro overlay now keeps its retail 128x128 ambient data/static layers tiled at native scale across the expanded GUI canvas, and edge-hugging detail widgets anchor to the appropriate wide/tall screen sides.
- [x] Stock split-map transitions now preserve Raven entity filters such as `game/storage1 first` / `second` across map commands, autosaves, savegame restore, and loading screens, and bare launches such as `map game/storage1` or `map game/process1` now default to the first filtered visit when the retail mapDefs only provide `first`/`second` variants. This restores the filtered map state, `mapDef` title, levelshot, autosave name, and load-size metadata instead of falling back to the raw map path.
- [x] Split-map transition validation now has a named campaign profile that triggers the real end-level targets from `game/mcc_2` through `game/storage1 first`, `game/storage2`, `game/storage1 second`, and into `game/tram1`, asserting the active map/filter after each load so first/second state regressions fail loudly.
- [x] Save-game command handling now matches retail Quake 4 more closely: map-start autosaves use stable map/entity-filter slot names, quicksaves rotate through tracked quick slots, `saveGame checkPoint` creates protected `Checkpoint0`/`Checkpoint1` slots instead of a manual `checkPoint` save, restart prefers the latest checkpoint slot, and saves are blocked after a failed objective like retail.
- [x] Save-game loading is more stable: openQ4 now restores the game-state alert fields in the same order they are saved, validates save, entity, GUI, script, animation, physics, event, and collision-cache payloads before rebuilding runtime state, and reports truncated or malformed save files cleanly instead of continuing into a crash-prone misaligned stream.
- [x] Main menu background art now cycles through five fixed single-player campaign level images from menu-owned assets instead of randomly scanning loadscreen shots; the images are precached for faster menu opening, keep a subtle slow pan/zoom, and use a static edge-fade mask so separate expansion side/top/bottom images are no longer required.
- [x] Startup/loadscreen placeholder now hands off to the main menu automatically after 3 seconds, main-menu entry transitions use a short black fade-in stretched to native screen extents, and startup logo videos can be skipped with default-on `com_skipLogoVideos 1`.
- [x] Removed the depth-aware camera-flare graphics option and its post-process runtime path; stock-authored material flare surfaces remain supported through the existing material deform path.
- [x] Material handling fixes completed; engine startup no longer depends on custom material script overrides in repo `q4base/`.
- [x] Startup material/icon sizing now tolerates stock decls without drawable stages, preventing an application-load crash during UI icon registration.
- [x] Decl parse dispatch now follows the retail SDK no-caching path through `idDeclBase` again while material and sound shaders expose explicit three-argument parser overrides, preserving stock material stage/image parsing and preventing fully black map rendering after level load.
- [x] Decl-manager folder registration and packed-decl writing now mirror more retail bookkeeping: startup logs per-type load time/text memory again, and packed `.decls` writes store the decl's current linear-list slot with the retail index fallback.
- [x] Decl late-load diagnostics now match retail more closely by warning when uncached decls are parsed after startup/level precaching outside active editor sessions.
- [x] Decl editor/helper safety improved: `CreateNewDecl` now mirrors retail null-tolerant filename/default-definition handling, and `DeclByIndex` range errors report the decl type and requested/max indices.
- [x] Legacy PDA/email/video/audio decl families are registered engine-side again with retail list/print command wiring and parser stubs, restoring Quake 4's full decl type surface for game and tool lookups without adding content overrides.
- [x] Retail light material parsing parity improved: `lights/*` material stages and `lightFalloffImage` entries now load through the renderer's light image bucket instead of generic material image classification, restoring Quake 4's intended projection/falloff handling for stock map lights.
- [x] Retail programmable material decl parsing now recognizes Quake 4's symbolic `fragmentMap` bindings for `lightfalloffImage`, `lightImage`, `ambientNormalMap`, `normalCubeMap`, and `specularTableImage`, keeping light-driven slots tokenized instead of misloading them as ordinary images.
- [x] Retail guide-template decl wiring restored: `rvDeclGuide` now has its retail parse/evaluate/remove-bracing implementation, guide reloads clear the previous table before reparsing, duplicate guide names are rejected like retail, and `guide`/`inlineGuide` material expansion now shares the same parameter substitution path.
- [x] Decl validation/tool parity improved: the base `idDecl::Validate` wrapper now delegates through the decl base like the Quake 4 SDK, the local decl fallback validates by parsing a temporary decl instead of accepting every block, validation runs now assert the retail `DoingDeclValidation()` editor bit so parser-side hard errors can downgrade to warnings, entityDef media caching observes the recovered retail editor-mask bits, material/sound decls expose the retail validation slot, sound shader decls can rebuild text for `resaveDecl` tooling, and the engine-side `idCommon`/UI manager surface again exposes Raven's decl/tool hooks used by SDK validation, `editDecls`, model-view, debugger, and always-think GUI paths.
- [x] Game-library decl wiring tightened: SP `model` and `camera` decl validation now performs temporary-decl parsing instead of accepting every text block, SP validation-only model/animation failures downgrade through the same `DoingDeclValidation()` warning path as MP/the SDK, SP `def`/`af` folder registration uses the same Raven wrapper path as MP/the SDK, and camera validation now allocates the dedicated `DECL_CAMERADEF` slot rather than the SDK's stale model-def allocation.
- [x] Packed decl writer/export support improved: generated game-section `.decls` files can include the registered `model` and `playerModel` decl slots alongside entity/camera/AF/export data in openQ4 mode, while the interactive `writeDeclFile` command is exposed from the same loose-decl startup branch as retail.
- [x] Decl SDK/header wiring tightened: restored PDA/email/video/audio decl declarations now expose both retail parse entry points to the companion game-library headers, and optional decl-folder scans tolerate absent file lists while continuing recursive discovery.
- [x] Player-model decl parsing now follows the recovered retail rules more closely: malformed unquoted keys warn/default instead of being accepted, only canonical `def_head` / `def_head_ui` fields populate head data, and the fallback definition matches Quake 4's minimal marine/Kane-head pair.
- [x] Playback decl runtime sampling now follows retail timing more closely: recorded paths are resampled on finish, offset/angle queries return the playback expiration state, and button/impulse callbacks preserve their event offsets for game playback drivers.
- [x] BSE effect decl parsing now follows retail keyword handling more closely: the effect decl exposes the retail no-caching parser slot used by the decl manager, effect/segment/particle tokens are case-insensitive, `doubleVision`/shake/tunnel segment IDs use the shipped ordering, malformed effect bodies report failure at the same closing-brace boundary, trail segment lookups are case-insensitive, effect duration bounds rebuild on every finish, invalid particle-template entries warn/recover like retail, empty particle models fall back to `_default`, and overfull spawned-effect lists warn instead of terminating the client.
- [x] Game-library decl validation now shares the engine's temporary-decl validation/free helpers so SP/MP model and camera validation reject defaulted parses and clean allocated decl/base pairs consistently, and the companion `playerModel` header exposes the same retail parse overload surface as the engine.
- [x] Sound-shader decl parsing now reads the retail `s_maxSoundsPerShader` cvar, so stock configs and quality presets control per-shader sample admission instead of being ignored by the old openQ4-only `s_maxSamples` name.
- [x] Decl command tooling now follows retail default-creation semantics for `resaveDecl <type> <name>` and `touch <type> <name>`, sharing the same valid-type listing and materializing/defaulting named decls through `FindType` instead of treating absent loose declarations as hard misses.
- [x] Guide-template decl expansion now follows retail consumption semantics: found `guide` and `inlineGuide` calls are consumed even when their argument list does not match, inline-guide replacement runs as a single pass over the original definition, and missing inline-guide definitions return the retail failure state instead of partially succeeding.
- [x] Decl-file guide dispatch now follows the retail loader path in loose, packed, and lazy packed-stub parses: `guide` decls run the guide expansion attempt and the inline-guide pass without treating a failed expansion return as a skip, and duplicate loose decl definitions warn unconditionally like the shipped engine.
- [x] Decl source replacement now follows retail editor/save wiring: invalid source files report an empty filename, external-modification checks close their read handle before returning, and `resaveDecl`/decl-editor writes target `fs_devpath` instead of the launch CD path.
- [x] Retail guide parsing parity advanced: `guides/*.guide` now loads through a recovered guide-table path, guide creation shares the retail `GetNewGuide` helper, and unrelated top-level guide-file tokens are tolerated like the shipped engine instead of aborting startup parsing.
- [x] Packed decl serialization and intake now track retail more closely: loose/packed decl loaders let `ParseBracedSectionExact` consume non-guide bodies directly, inline-guide expansion runs from the same post-body step, and generated packed decl entries always write the retail `type name` header before the stored body or stub.
- [x] Guide-template substitution now follows retail's evolving-definition replacement loop, preserving correct behavior when guide arguments overlap with parameter names or later replacements shift the body, and the obsolete pre-retail guide preprocessor has been removed.
- [x] Loose decl reloads now honor retail's renderer-side `image_writeProgramImages` parse trigger, allowing the program-image generation path to force newly loaded decls through `ParseLocal()` once the renderer is running even when they have not already been referenced this level.
- [x] The `checkDecls` command now follows retail's reparse gate for every loaded decl that is not already `DS_PARSED`, so defaulted decls are checked again instead of being skipped as if they were clean.
- [x] Packed decl loading now initializes placeholder decl records completely before indexed replacement, preventing the retail `com_SingleDeclFile`/`checkDecls` path from dereferencing debug-fill memory when sparse packed indexes are replayed.
- [x] Decl reload/tooling diagnostics now follow the retail path more closely: `reloadDecls` reports per-file progress, and decl text-memory totals walk `GetNumDecls`/`DeclByIndex(false)` so packed sparse indexes and editor summaries share the normal decl access surface.
- [x] Guide-template edge cases now match retail parser recovery more closely: malformed parameter comma counts warn under the same rule, inline-guide body unbracing follows the shipped two-step trim, and missing guide names no longer skip ahead past the lexer position retail leaves for subsequent parsing.
- [x] The packed-decl cvar surface now matches retail wiring more closely: `com_SingleDeclFile` is exported through the shared engine headers instead of staying private to the decl manager, so renderer/session/game code can bind to the same cvar symbol as the SDK headers expect.
- [x] Packed-decl startup naming now follows retail's default asset-log handoff: the filesystem seeds `assetlogs/default` during init, and the decl manager derives `default.decls` from the active asset-log basename instead of falling back to openQ4's older `decls.decls` name.
- [x] Asset-log ownership now follows retail's decl-packaging handoff more closely: `SetAssetLogName` renames the active asset log without implicitly clearing collected entries, leaving log reset/write ownership to the explicit asset-log paths.
- [x] Retail programmable material-decl parsing now recognizes Raven GLSL `shaderParm` and `shaderTexture` built-ins from guide-expanded stages, including light origins/projection/falloff, color modulation/addition, diffuse/specular colors, matrix rows, shared lookup images, and dynamic light image slots.
- [x] Retail custom-lighting material stages now stay out of the ambient pass and run through the per-light interaction path, with material-authored GLSL programs receiving Quake 4 interaction uniforms, dynamic light textures, stable tangent-space attribute bindings, and the same packed MD5R ambient-cache recovery used by the other GLSL interaction paths while shadow-map lighting falls back to the stencil path for those custom material programs.
- [x] Retail lighting parity restored for distance-cull portal fades, Raven special-effect pass ordering, ARB2 specular scaling, and `noSelfShadow` stencil-shadow routing.
- [x] Retail light/interaction parity improved again: fog/blend lights now honor Quake 4's `noFog` interaction contract, sound-driven light/material shader expressions once again receive `referenceSoundHandle` amplitude inputs, entity ambient submission now honors retail `r_lod_entities` scissor-LOD gating for tiny on-screen coverage, retail `r_useEntityScissors` behavior is once again enabled by default so entity scissor tightening matches stock Quake 4 out of the box, view entities now follow retail's sort-by-model submission order before ambient/light interaction work, authored light detail levels once again gate view-light admission through retail-style `r_lightDetailLevel`, light/shadow submissions now honor retail `r_limitBatchSize` tiny-batch dropping before drawsurf allocation, interaction shadows now respect retail shadow-LOD / `noDynamicShadows` gating instead of keeping distant or explicitly disabled dynamic shadows alive, and light/shadow routing once again keys off the original interaction material instead of letting shader overrides change `noSelfShadow` ownership.
- [x] Retail shadow admission parity now carries through openQ4's dedicated shadow-map caster path: opaque and experimental translucent shadow-map casters honor Quake 4's cached shadow-LOD decision instead of resurrecting distant dynamic shadows, the randomized retail shadow-LOD frame hold now uses Quake 4's recovered 15-bit `1/32768 * 1000` range consistently across supported platforms, shadow-map-only ambient casters keep their interaction alive without requiring a stencil silhouette volume, cvar-gated shadow-map features can request the cached LOD decision lazily without treating "not needed yet" as "rejected", inert non-silhouette surfaces no longer refresh the retail shadow-LOD hold unless they can feed a dedicated shadow-map caster, shadow-map local/global ownership now follows the same original-material `noSelfShadow` split as the stencil path, point-light shadow maps keep legacy `globalShadows`/`localShadows` in mixed dedicated/fallback caster scenes, mapped lights now fall back to the retail stencil path when every opaque caster in a legacy/prelight chain is skipped instead of sampling an empty shadow map, receiverless lights now discard dedicated shadow-map caster chains alongside retail stencil shadow chains before scene-packet/planner work, and static/prelit geometry, stencil fallback, and the modern shadow planner keep their existing behavior.
- [x] Retail packed MD5R light-submission parity advanced again in the current hybrid renderer: packed ambient surfaces, packed-backed light interactions, and packed shadow-map casters now refresh frame-temp draw/index data per visible frame instead of forcing long-lived classic cache ownership, interaction `lightTris` retain retail packed ownership metadata in the front end, and packed temp-cache submissions no longer trip `idVertexCache::Touch()`.
- [x] The final tracked renderer light-parity gap is now closed in code: packed MD5R depth, fog, ambient, material-stage, interaction, and stencil-shadow submission now bind retail-style MD5R vertex programs and draw packed buffers directly instead of falling back through transient classic draw-vertex uploads, while material parsing also resolves the retail `md5r*.vp` stage-program aliases needed by those packed paths.
- [x] Added a live renderer light-report tool (`r_showViewLights`) that prints detailed per-light diagnostics for lights affecting the current view origin/player position.
- [x] Added a persistent visual overlay for `r_showViewLights` (`r_showViewLightsVisuals`) that draws each last-reported light's origin marker, color, and radius volume in-world until the next report refresh.
- [x] Retail `deform rectsprite` material support restored so shipped Quake 4 multiplayer flag-display shaders follow the renderer's rectangular autosprite path.
- [x] Menu rendering issues fixed.
- [x] GUI font rendering now follows retail Quake 4 font metrics more closely: stock `.fontdat` files load with their native glyph bearings, advances, point-size scaling, no extra draw-time vertical glyph bias, guarded glyph atlas edge sampling, no-picmip atlas materials, retail-style DXT1 green-alpha font atlas upload/mips, `.fontdat` atlas-token canonicalization, embedded icon sizing/UV handling, shared Q4 color/repeat/icon escape parsing, RGB escape alpha preservation, retail-style icon measurement/escape-spanning cursor accounting, chat-window wrapping and parse-flag behavior, field edit spacing behavior, bind/choice/list/edit caller spacing parity, list tab baseline alignment, wrapped edit line-height/scrollbar wrapping behavior, end-of-text cursor handling, single-pass authored/choice/simple-window foreground text rendering, retail simple-window eligibility for static labels, transformed glyph and GUI-quad renderer clipping bounds, deterministic GUI quad vertex payload, font language/cache lifecycle cleanup, text spacing/style save-load persistence, and MP HUD wrap-index support while preserving openQ4's wide/tall aspect-correct UI expansion. The new `uiFontParitySelfTest` locks down retail glyph scaling, embedded icon sizing modes, RGB escape alpha behavior, baseline/alignment, expanded wide/tall canvas mapping, clipped UV trimming, font atlas upload options, and the tight stock HUD radio `fonts/marine` sizing that keeps the `incoming transmission` label intact instead of vertically clipping it against its own text boxes.
- [x] Settings menus now present cleaner Display/Game pages with compact single-display spacing, smooth clipped scrolling, corrected scroll extents, and Proper Case openQ4 setting labels while keeping section headings uppercase.
- [x] Settings-page scrolling now responds to mouse wheel, PageUp/PageDown, and controller shoulder paging on the active settings tab, keeps thumb and paging state in sync, and exposes the full Game settings scroll range.
- [x] SDL3 backend integrated as the default platform path (legacy Win32 backend remains transitional).
- [x] Meson + Ninja build system introduced as canonical build path.
- [x] External dependencies moved to Meson subprojects/wraps (`sdl3`, `glew`, `stb_vorbis`, `openal-soft-prebuilt`).
- [x] Vendored GLEW updated to 2.3.1 (both `subprojects/glew` and `src/external/glew`) while preserving local static-link/include-path compatibility tweaks.
- [x] Vendored OpenAL Soft updated to 1.25.1 headers/defs/import libs (both `subprojects/openal-soft-prebuilt` and `src/external/openal-soft`), with Win64 runtime DLL refreshed under `src/external/openal-soft/bin/win64/OpenAL32.dll`.
- [x] Dependency refresh validated with clean Meson reconfigure (`setup --wipe`) and successful rebuild in `builddir`.
- [x] Ogg Vorbis (`.ogg`) playback support integrated (decoded via `stb_vorbis`).
- [x] C++23-targeting baseline enabled on MSVC (`cpp_std=vc++latest`).
- [x] MSVC 2026 toolchain direction documented and implemented as an optional enforceable baseline (`-Denforce_msvc_2026=true`).
- [x] Meson setup wrapper improved to auto-recover missing/stale build directories and avoid VS tool discovery null-crash.
- [x] Windows SDL3 key/input parity improvements: backspace fix, control-char synthesis, locale-aware RightAlt behavior.
- [x] Manual key matrix audit completed and documented for console, GUI edit fields, chat, binds, numpad, and modifiers.
- [x] GUI scaling behavior updated to preserve uniform/aspect-correct rendering on window resize.
- [x] Engine-side console/UI relayout now handles wide and narrow/tall aspect ratios, with live updates on screen size/aspect changes.
- [x] Console mouse handling now uses native console-space cursor routing/drawing, and adds archived `con_height` control for the console open height (`0.1` to `1.0`, default `0.5`).
- [x] Platform/architecture roadmap documentation added for Windows/Linux/macOS direction with x64 baseline.
- [x] Legacy/redundant build-system artifacts reduced (CMake path retired from active source tree).
- [x] BSE manager/renderer lifecycle contract restored: effect completion now propagates correctly through `ServiceEffect` -> `UpdateEffectDef`, preventing immediate client-effect teardown and enabling proper expiry reporting.
- [x] BSE sound capability path restored at render-world boundary: `EffectDefHasSound` now delegates to decl metadata and manager checks instead of unconditional false.
- [x] BSE segment runtime no longer compiles as pure no-op stubs; core segment timing/attenuation/check/update/count plumbing has been re-enabled as the phase-4 runtime foundation.
- [x] BSE game-lib network/event parity restored: `EVENT_PLAYEFFECT_JOINT` no longer hits an assert placeholder, receive paths now validate decl/filter/rate-limit consistently, and networked effects restore gravity assignment parity.
- [x] BSE save/restore stability improved for active effects by clearing stale `referenceSoundHandle` on restore so emitters are safely reallocated.
- [x] Temporary BSE visibility fallback added (`bse_fallbackSprite`, default `1`): active client effects now emit a sprite-backed render entity until full particle-surface rendering is fully restored.
- [x] BSE segment activation timing fixed: `rvBSE::Service` no longer applies a large negative per-segment spawn offset that delayed segment starts by multiple seconds.
- [x] BSE segment spawn lifecycle restored for `SEG_EMITTER`, `SEG_TRAIL`, and `SEG_SPAWNER`, including attenuation-aware interval/count spawning and loop-time progression tracking.
- [x] BSE trail/child spawn placement improved: non-zero spawn offsets now propagate into particle init positions, and trail segments spawn using interpolated local movement offsets.
- [x] BSE moving-emitter transform parity improved: non-locked particle position/velocity evaluation now converts from spawn-space into current effect-space using stored spawn origin/axis.
- [x] BSE owner wind propagation restored by transforming `renderEffect_t::windVector` into local effect wind each update.
- [x] BSE decal segment path restored from no-op to active world projection (`SEG_DECAL` now builds/projections decal winding via `ProjectDecalOntoWorld` using sampled spawn size/rotation).
- [x] BSE spawn ordering parity improved: runtime particle insertion is no longer unconditional LIFO; linked segments preserve stable chronological/end-time list order while complex segments retain front-insert behavior.
- [x] BSE segment draw-order refinements applied for linked-strip rendering: depth-sort is disabled for linked strip topologies and initial strip index budgeting is handled explicitly.
- [x] BSE runtime sort utility implemented (`rvSegment::Sort`) with stable depth ordering for deterministic per-segment ordering when used.
- [x] BSE network/entity receive rate-limiting corrected by removing duplicate cost consumption (`Filtered` already applies category rate checks; explicit second `CanPlayRateLimited` checks removed).
- [x] BSE particle spawn-parameter sanitization restored (`rvParticleTemplate::FixupParms` no longer compiles as a no-op stub).
- [x] BSE segment attenuation scaling parity restored: emitter interval/count attenuation now uses `bse_scale` interpolation semantics.
- [x] BSE particle cap parity restored: `bse_maxParticles` now drives runtime segment allocation/count clamps instead of compile-time-only `MAX_PARTICLES` paths.
- [x] BSE owner-state parity improved: `mLightningAxis` is now derived from current origin/end-origin direction with stable basis fallback.
- [x] Temporary BSE trace spam removed from hot runtime paths (spawn/expire/remove and segment render skip/state traces), and renderer counter cvar default restored to off (`bse_frameCounters=0`).
- [x] Full clean rebuild validation completed in `builddir/` with both `openQ4.exe` and `openQ4-ded.exe` linking successfully after BSE parity updates.
- [x] BSE particle gravity parity restored: template `gravity` ranges now contribute spawn acceleration in `rvParticle::FinishSpawn`.
- [x] BSE debris parity advanced: debris particles now spawn client moveable entities (`entityDef`) through `game->SpawnClientMoveable` and stop CPU-side particle rendering.
- [x] BSE shader-parm safety defaults hardened: client effects now initialize RGBA/brightness/timeoffset in `rvClientEffect::Init`, with zeroed-parm fallback in `rvBSE::UpdateFromOwner`.
- [x] BSE template runtime contract completed by implementing missing declared helpers (`rvParticleTemplate::Compare`, `GetTraceModel`, `GetTrailCount`, `ShutdownStatic`).
- [x] BSE unlocked-bounce matrix reprojection parity improved: post-impact velocity/position persistence now uses inverse `current->init` axis mapping with origin-delta compensation, avoiding frame-space double transforms in moving/rotating emitter paths.
- [x] BSE owner-kinematics parity improved: `rvBSE::UpdateFromOwner` now preserves last valid owner velocity across same-timestamp updates instead of zeroing `mCurrentVelocity` on tiny frame deltas.
- [x] BSE oriented-particle matrix parity improved: oriented quad corner ordering/signs now match decompiled transform basis usage, removing mirrored orientation in rotated oriented-particle paths.
- [x] BSE sprite and oriented particle rendering now follows retail Quake 4's cross-quad sizing and center-normal vertex payload, restoring walker shield-hit ripples/sparks and related stock impact effects to their intended scale and orientation.
- [x] BSE effect culling now avoids stale portal-visibility rejects by requiring cached rendered geometry before pre-service/pre-render area or frustum culls can skip work, then rechecking with freshly rebuilt effect bounds; visible map-authored effects such as `game/airdefense1`'s `func_fx_24` no longer pop in late when turning toward them.
- [x] Startup command parsing hardening completed: oversized `+wait` stress launches no longer crash during early `StartupVariable` cvar processing (command-line overflow/drop handling and `idCmdArgs::AppendArg` bounds checks tightened).
- [x] Multiplayer server-side hitscan lag compensation added with configurable rewind controls (`net_mpLagCompensation`, `net_mpLagCompMaxMS`, `net_mpLagCompBiasMS`) and server diagnostics (`net_mpLagCompDebug`).
- [x] Multiplayer non-local prediction mode is now runtime-selectable through `net_mpPredictMode` (`0` legacy limited behavior, `1` enhanced per-frame prediction).
- [x] Scope/zoom handling documentation updated to reflect multiplayer zoom stability and scope yaw alignment behavior.
- [x] High-refresh view interpolation now includes tunable local first-person biasing (`pm_presentViewBias`) so the camera, FOV, and view weapon spend less time a full tic behind at high presentation rates without changing authoritative gameplay timing.
- [x] High-refresh local first-person biasing was retuned so the default interpolation path no longer surges to the newest snapshot and stalls mid-tic; `pm_presentViewBias` now remains an opt-in bias that eases forward more gently when needed.
- [x] High-refresh first-person weapon FX now stay aligned with the interpolated local/spectated camera on the fire frame: view-weapon muzzle flashes refresh in presentation space immediately, and hitscan tracer/path origins sample the presentation-space muzzle instead of the last authoritative pose.
- [x] High-refresh multiplayer carrier/powerup halo lights now follow interpolated player presentation on repeated-state frames instead of stepping only at the 60 Hz simulation tick, keeping CTF/powerup light halos aligned with moving carriers.
- [x] High-refresh vehicle crash/scrape effects now interpolate persistent world-space crash FX between collision snapshots on repeated-state frames, keeping scrape contact visuals smoother when vehicles slide along world geometry.
- [x] High-refresh projectile fly trails and player powerup effects now keep their presentation-time attenuation/origin parameters in sync with interpolated movement on repeated-state frames instead of stepping only when the 60 Hz gameplay tick rewrites those effects.
- [x] Multiplayer joiners now reconstruct terminal non-predicted projectile snapshots into visible detonate/impact FX instead of silently hiding those projectiles when they arrive already exploded, restoring host-fired projectile hit visuals on clients.
- [x] Multiplayer clients now receive authoritative hitscan impact FX through a dedicated remote-impact packet while the legacy hitscan trace message continues to drive path/tracer replay; that replay now bypasses local effect-rate throttling, reaches the firing remote client as well as other viewers, preserves the authored bullet/shotgun impact color, and still reapplies tint for railgun-class impacts that explicitly opt into hitscan coloring.
- [x] High-refresh listen-server view-weapon movement offsets now collapse duplicate same-tic acceleration samples during prediction reruns, preventing forward/side movement input changes from stacking extra shove into the local first-person gun after the earlier view-bias and turn-history fixes.
- [x] High-refresh listen-server view-weapon presentation no longer feeds render-only interpolated weapon poses back into shared weapon state, preventing the local first-person gun from regaining the forward lunge while repeated-state draw frames refresh muzzle-flash / flashlight / GUI-light alignment.
- [x] Multiplayer draw now reuses the same render-prep path as single-player, so the local or spectated first-person view recalculates its render view and refreshes presentation-space player/world/view-weapon transforms immediately before `RenderPlayerView()` instead of drawing the weapon from a stale simulation pose during listen-server movement.
- [x] High-refresh first-person muzzle flashes and other bound view-weapon firing FX now refresh from the corrected draw-time presentation pose on both simulation and repeated-state frames, so they no longer stick at the pre-correction gun position on the fire frame after the listen-server lunge fix.
- [x] High-refresh lightning-gun follow-up was narrowed to the local first-person beam only: `trailEffectView` now refreshes from the corrected presentation-space muzzle/chest joint and a presentation-time local beam trace on both simulation and repeated-state render frames, while the authoritative world/chain-lightning effects remain on the normal gameplay update path.
- [x] High-refresh bespoke visual-owner follow-up now refreshes gib skeleton side-models and security-camera model presentation on repeated-state frames, keeping those visuals aligned with interpolated transforms instead of stepping only at the 60 Hz simulation cadence.
- [x] High-refresh alternative camera/view-source follow-up now refreshes remote entity render views, security-camera feeds, portal-sky/playback cameras, and steam-pipe side models from presentation-space or interpolated repeated-state data instead of leaving those views and side visuals pinned to the last 60 Hz simulation sample.
- [x] High-refresh bespoke-visual follow-up now interpolates SP/MP `idMultiModelAF` side-model render defs on repeated frames and aligns the MP jump-pad activation FX origin with the SP centered-physics path, closing another pair of effect/render-owner presentation gaps that still sat outside the base entity refresh hooks.
- [x] High-refresh brittle-fracture callback models now rebuild from interpolated shard transforms on repeated-state frames, keeping dropped shard geometry smooth through shatter/fall/settle motion instead of stepping only on the 60 Hz simulation tick.
- [x] High-refresh weapon render lights now stay aligned with interpolated weapon/player presentation on repeated-state frames, keeping active muzzle-flash, world muzzle-flash, flashlight, and weapon GUI light defs from stepping at the 60 Hz simulation tick.
- [x] Ragdoll activation quality improved without changing the fixed 60 Hz simulation cadence: startup now keeps owner/world motion, handles initial penetrations more cleanly, and preserves slightly richer contact support for grounded corpses.
- [x] Rigid-body physics timing-safe quality pass applied without changing the fixed simulation cadence: angular velocity handoff now respects world inertia, water is handled as drag instead of a one-time collision-state hack, rigid-body contacts keep richer deduplicated support points, and impacts now preserve time-of-impact momentum while consuming a small bounded amount of leftover fixed-step time.
- [x] Script compiler x64 pointer-temp parity fix ported from OpenD3: right-associative indirect-expression retagging now guards 4-byte object-ref temp vs 8-byte pointer temp storage mismatch by allocating pointer-sized result defs when needed, preventing trigger/door script chain corruption.
- [x] Retail AAS placeholder parity restored: stock dummy `.aas` files now load/discard like retail instead of warning-spamming and failing stock map init, and AAS tactical data is cleared correctly between loads.
- [x] Bloom stability and quality improved: live `r_bloom`/`r_bloom*` changes now use the offscreen scene-target path immediately, scratch render targets rebuild their FBO attachments safely after runtime reallocations, map handoffs no longer rely on the fragile back-buffer bloom path, and bright-pass extraction now keeps only highlight energy so broad lights stop producing solid white bloom discs.
- [x] Manual release packaging builds are healthy again: the engine restored the shared `idCommon` timing/error/tool accessors expected by the SP/MP game libs, fixing the cross-platform release-build break that stopped the `v0.1.011` workflow before artifacts were produced.
- [x] High-refresh mover riding is now stable on lifts such as `game/storage2`: the player keeps descending mover carry, ignores tiny mover-seam pseudo-landings/step jolts, and interpolates first-person view state relative to the mover so the camera stays smooth above 60 FPS instead of vibrating against platform motion.
- [x] Retail trigger compatibility improved for `game/process1 second`: the intro lift now uses retail-style precise clipping for hurt triggers while keeping the x64 bounds fallback for non-damaging player triggers, preventing a stock shaft kill volume from falsely killing the player during the downward ride.
- [x] Lift panels in `game/process2` are more reliable: in-world GUI focus now uses the actual traced panel hit, retail-style unique GUI reuse keeps panels such as `func_elevator_1_gui` interactive, and ignored no-op/disabled floor commands refresh the display instead of leaving it stuck on an in-transit state.
- [x] High-refresh projectile and weapon-hit impact regressions were corrected by keeping client-predicted projectile collision/explode work on authoritative game ticks only while repeated-state redraws use the dedicated projectile interpolation path, preventing lingering rocket models and impact/scorch FX from replaying every render frame after a hit.
- [x] Renderer effect lifetime replay regression fixed: expired one-shot BSE impact effects now stay finished until their client-effect owner frees the handle, preventing bullet marks, scorch decals, and hit sounds from being recreated every frame after a hit.
- [x] Phase 4 repeated-state rendering overhead reduced: SP/MP scene refresh no longer scans all spawned entities every redraw for projectile interpolation, and active entities now skip repeated-state transform/non-model refresh work unless they actually have presentation deltas or interpolation-sensitive auxiliary visuals, recovering a major `agame/airdefense1` FPS regression introduced during the high-refresh work.
- [x] High-refresh BSE overhead reduced again: repeated-state client effects now keep renderer owner-time on authoritative game ticks instead of wall-clock presentation time, avoiding render-rate ambient effect spawning on maps like `agame/airdefense1`, and renderer effect servicing now runs once per rendered frame instead of repeating per view.
- [x] Repeated-state client-entity work is now pruned more aggressively in SP/MP: static client models and client effects no longer rerun redraw-time presentation work by default, while bound client entities only stay on the high-refresh path when their owner actually has an interpolated transform delta and client moveables stay there only while their own transform/scale interpolation is active.
- [x] Collision-model retail parity improved again: `.proc` loading now matches Quake 4's looser version-token behavior, CM precache requests stay on the `.cm`-only path instead of generating render-model collision data, and extension-mismatched authored collision caches (for example `.lwo`/`.ase` names requested through runtime render aliases) now reuse the existing loaded CM without renaming or duplicating cache entries.
- [x] Collision-model loader parity improved again: CM `.proc` and `.cm` parsing now use Quake 4's binary-aware lexer path instead of bypassing it, restoring compiled-companion fallback behavior (`.procc` / `.cmc`), normal search-path precedence, retail-style CM loader diagnostics, retail proc-clip slot reservation during map builds, retail-style `.cm` serialization that no longer forces synthetic proc-clip models into saved map collision caches, retail-style binary companion generation (`.cmc`) when `com_BinaryWrite` is enabled, stock load pacifier updates around collision-data generation, retail-style fatal map-load handling when the required `.proc` build data is missing, explicit map-entity CM exports back on `fs_devpath`, and the retail PROC/AAS out-of-date overlays during content bring-up.
- [x] Collision-model trace/debug parity improved again: rotation and contents paths now restore retail-style `materialType` propagation, SP/MP share the retail two-argument CM debug interface with player-view-oriented replay hooks, retained translation/rotation failure replay now mirrors retail debugging more closely, and stock `game/airdefense1` no longer crashes at player spawn/level load when retail-style CM material-type lookups run against parsed `.cm` data.
- [x] Level-load filesystem robustness improved: empty-path `ReadFile` probes now resolve as normal missing-file checks instead of raising a fatal dialog, which unblocks stock map startup paths such as `game/airdefense1` and keeps the runtime log alive for any follow-up diagnostics.
- [x] Collision-model diagnostics parity improved again: local CM `ModelInfo()` reporting is no longer a stub, single-player restores the retail `collisionModelInfo` debug command alongside multiplayer, `Contacts()` once again follows retail's translation-only contact sweep semantics, and SP validation on `game/airdefense1` plus `collisionModelInfo all` now completes cleanly with the updated CM debug/reporting paths.
- [x] Collision-model cache-write parity improved again: runtime-generated `.cm` caches now follow retail Quake 4's `fs_devpath` destination instead of being diverted to `fs_savepath`, keeping generated CM outputs on the normal authored/dev override path alongside the existing map-entity export behavior.
- [x] Collision-model render-source parity improved again: `LoadRenderModel()` now matches retail Quake 4's authored-CM source-model whitelist by admitting only `.ase`, `.lwo`, and `.obj` when generating collision data from render models, dropping the extra `.mdr` / `.dae` CM-generation path that was broader than retail.
- [x] Single-player clip-query parity improved again: `idGameLocal::ClipModelsTouchingBounds()` no longer falls back from non-zero clip worlds into world `0` for trigger queries, so the SP bounds-query path now matches multiplayer and retail behavior instead of keeping the temporary openQ4 multi-instance trigger override.
- [x] MD5R parity improved again: SoA `Pos3Swizzled` vertex buffers now decode into usable packed geometry instead of falling back to metadata-only loads, `MD5RProc` companions now preserve and round-trip the real retail map CRC token instead of a stub value, and retained MD5R vertex exports now keep specular-color / point-size fields instead of discarding them.
- [x] Decal parity improved again: packed MD5R surfaces now prefer a retail-style native sil-trace decal projector over the transient draw-vertex materialization fallback, while still falling back safely when packed mesh state is incomplete; the fallback now reuses the current live draw geometry when available so dynamic skinned recipients do not drop back to bind-pose decal projection, the native packed path now only runs on true packed sil-trace buffers instead of misusing draw-order `silTraceVerts` mirrors, decal fade cleanup no longer needlessly invalidates cached tangent/plane data every frame when the decal mesh itself did not change, and decal draw-surfs now snapshot vertices plus per-stage color data into one retail-style transient upload block instead of splitting them across separate temp cache allocations.
- [x] SDL3 keyboard layout parity improved: printable key events now use SDL3's active-layout translation instead of the fixed US scancode map, non-English console-toggle lookup now follows the translated grave/console scancode again, and UTF-8 text input is tracked as decoded codepoints in the SDL3 event path rather than a stale byte-splitting backlog item.
- [x] POSIX startup now enforces the same single-instance guard as Windows by holding a per-user advisory launch lock, while `sys_allowMultipleInstances 1` keeps multi-instance testing available when explicitly needed.
- [x] Retail flare/material deform parity improved again: ambient submission no longer clips `deform flare` / sprite-style materials to tiny authored entity scissors, so stock map-authored effects such as `game/storage1`'s moving `beamBlastFlare` render as expanded flare geometry instead of a boxed white square; the flare deform path also restores retail prim-batch handling and cleared-temp-vertex setup.
- [x] Retail decl folder registration parity improved: recursive decl folder scans now register each child folder as its own folder/default-type pair before loading files, and `DECL_MODELEXPORT` ownership is back on the game-library `model`/`export`/`camera` registration path instead of the framework pre-claiming the parser-only export slot.
- [x] Direct no-post scene rendering now clears the backbuffer before drawing the world, preventing full-screen GUI/material overlays such as stock vehicle screen dirt from persisting after the GUI that drew them has stopped rendering.
- [x] Renderer restart and game-mode reloads are more robust: `vid_restart` and SP/MP engine reloads now release scene, post-process, shadow-map, BSE, and background filesystem state before rebuilding OpenGL, reducing black-scene and initialization failures after changing renderer settings or switching game modules.
- [x] Outdoor BSE-heavy scenes such as `game/airdefense1` now reject offscreen effect geometry from maintained effect bounds before rebuilding render surfaces, reducing frame pacing stutter while preserving authored smoke and effect rendering.
- [x] Windowed play is smoother in BSE-heavy outdoor scenes: new installs default windowed mode to borderless presentation and legacy Windows configs migrate once from the old bordered-window default, avoiding Windows/OpenGL swap stalls seen in `game/airdefense1`; BSE debris entity definitions are also precached when effects parse so first-impact debris media no longer loads during gameplay.
- [x] The `game/core2` Makron fight is more reliable on x64 builds: scripted flying turns now rotate toward their target without getting stuck, and the retail teleport phase can recover if its script handoff fails to finish so the boss no longer remains hidden and effectively undamageable.
- [x] Linux optimized builds now keep light-fixture glow colors stable while panning the camera, fixing the primary-color flicker seen on stock map fixtures such as `game/airdefense1`.

## macOS Evidence Gate

Complete this section before release notes claim macOS support beyond the current experimental Apple Silicon/arm64 status.

- [ ] `docs/dev/macos-signoff-evidence.md` has a current release entry for the candidate version.
- [ ] The evidence entry records run ID, archive path or artifact URL, archive SHA-256, validator command, validator result, openQ4 commit, `openQ4-game` commit, package artifact names, matching dSYM symbol artifact names, `SYMBOLS.txt` manifest status, signing/notarization status, architecture policy, CPU architecture, OS matrix role, macOS version, kernel, Xcode version, macOS SDK version, hardware model, graphics bridges, OpenAL provider, and known exceptions.
- [ ] The current matrix policy in `docs/dev/macos-support-matrix-policy.md` matches the release notes, `BUILDING.md`, `docs/dev/platform-support.md`, `docs/user/getting-started.md`, and packaged README wording.
- [ ] macOS floor-version evidence is recorded for the documented `macOS 11` floor, or the release remains experimental and notes that floor evidence is still pending.
- [ ] Latest-public-macOS evidence is recorded for the release candidate, or the release remains experimental and notes that latest-version evidence is still pending.
- [ ] The accepted archive was collected with `-RequireCompletedSignoffChecklist`.
- [ ] `python tools/macos/validate_signoff_archive.py <archive> --require-completed-checklist` passed for both OpenGL and Metal bridge result directories.
- [ ] `python tools/macos/record_signoff_evidence.py <archive> --version vX.Y.Z --update-index` updated `docs/dev/macos-signoff-evidence.md`.
- [ ] Each bridge report includes `renderer-smoke`, `renderer-mp-smoke`, and `renderer-matrix` evidence.
- [ ] The release follows `docs/dev/macos-renderer-backend-policy.md` and `docs/dev/macos-native-backend-containment-policy.md`: OpenGL and Metal bridge evidence are kept separate, the Metal package is not described as native Metal, and native Cocoa/OpenGL backend results are not counted as release support evidence.
- [ ] SP entered an in-game map on real Apple hardware.
- [ ] MP loaded `game-mp`, started `mp/q4dm1`, connected a local client, and exited cleanly.
- [ ] Dedicated server startup was covered or explicitly documented as unsupported for this release.
- [ ] Finder or Desktop launcher startup and terminal startup were both checked.
- [ ] The package layout contract in `docs/dev/macos-package-layout-and-release-policy.md` was followed: `openQ4.app`, `baseoq4/`, and loose runtime files stayed together as an adjacent package root.
- [ ] Runtime macOS packages contain `SYMBOLS.txt`, do not contain `.dSYM` bundles, and have matching `openq4-<version>-macos-arm64-<bridge>-symbols.tar.xz` artifacts recorded for crash symbolication.
- [ ] Launch from a mounted signed/notarized DMG was checked, or unsigned archive behavior was recorded as an experimental exception.
- [ ] Launch after copying the whole package payload to a user-writable location was checked.
- [ ] Moving only `openQ4.app` was checked and either worked or failed with a clear adjacent-runtime error.
- [ ] `fs_basepath`, `fs_cdpath`, and `fs_savepath` were confirmed in logs for Finder/copied package and terminal launches.
- [ ] Gatekeeper assessment passed for signed/notarized DMGs, or unsigned/unnotarized approval friction was recorded for development archives.
- [ ] First-class macOS release jobs used signed/notarized DMGs; unsigned `-unsigned.tar.gz` artifacts were published only as experimental/development fallback output.
- [ ] Keyboard, mouse, controller if available, audio output/device switching, windowed/fullscreen, selected display, and HiDPI/Retina checks were completed or recorded as exceptions.
- [ ] Curated release notes in `docs/dev/releases/vX.Y.Z.md` mention arm64-only support, unsigned/unnotarized package behavior if applicable, and any renderer, audio, input, package-layout, SP, MP, or dedicated-server limitation found during signoff.
- [ ] Curated release notes in `docs/dev/releases/vX.Y.Z.md` mention that the macOS Metal package is a bridge around the OpenGL renderer and do not imply native Metal or native Cocoa/OpenGL backend support.

## Carry Forward

- [ ] Run macOS `Signoff -MacOSGraphicsBridge both`, complete the manual hardware checklist, then run `CollectResults -MacOSRunId <id> -MacOSGraphicsBridge both -RequireCompletedSignoffChecklist` on real Apple hardware and attach the auto-collected, auto-validated `.tmp/macos-vm/results/openq4-macos-results-<run-id>.tar.gz` evidence for input devices, audio devices, display modes, SP gameplay, MP `renderer-mp-smoke`, and in-game OpenGL/Metal renderer coverage beyond hosted CI compile/link/package checks; record the accepted archive in `docs/dev/macos-signoff-evidence.md`.
- [ ] Replace temporary MSVC compatibility flag (`/Zc:strictStrings-`) with full strict-strings codebase compliance.
