From 135c01cc42f2be77acd70ace939b97e4f4085884 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 3 Sep 2026 08:43:30 +0100 Subject: [PATCH 1/2] Document the classic dynamic lights The implementation lives in openQ4-game (bfe7f45); this is the player- and release-facing side of it. The new guide explains what the feature adds over stock Quake 4, and gives the two things a reader actually needs to act on: where each color comes from in the shipped assets, so the choices are checkable rather than asserted, and the per-def override keys for content that wants something else. It also records the cost model and points at testClassicLight rather than making people fire a weapon at a wall to see the effect. Co-Authored-By: Claude Opus 5 --- README.md | 1 + docs/dev/release-completion.md | 1 + docs/user/classic-dynamic-lights.md | 103 ++++++++++++++++++++++++++++ docs/user/gameplay-settings.md | 15 ++++ 4 files changed, 120 insertions(+) create mode 100644 docs/user/classic-dynamic-lights.md diff --git a/README.md b/README.md index 86b38324a..1e6082156 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ Packaged support currently focuses on Windows, Linux x64, Steam Deck/SteamOS, pr - [Competitive Matches](docs/user/competitive-matches.md) (experimental) - match framework, rules, and warmup behaviour - [Demo Library and Multi-View Demos](docs/user/multiview-demos.md) - browse formats, use playback controls, and record or replay complete multiplayer matches - [Liquids](docs/user/liquids.md) - water, slime, and lava behaviour, and how to author them +- [Classic Dynamic Lights](docs/user/classic-dynamic-lights.md) - Quake II/III style dynamic lights on muzzle flashes, bright projectiles, and explosions - [Shadow Mapping](docs/user/shadow-mapping.md) - optional shadow-map settings and troubleshooting - [Light Grids](docs/user/light-grids.md) - advanced lighting guide for players and testers - [Advanced Screen-Space Lighting](docs/user/advanced-screen-space-lighting.md) (experimental) - bounded froxel volumetrics, SSR, SSGI, performance controls, and one-setting rollback diff --git a/docs/dev/release-completion.md b/docs/dev/release-completion.md index ff486c3eb..1af10f4fa 100644 --- a/docs/dev/release-completion.md +++ b/docs/dev/release-completion.md @@ -948,6 +948,7 @@ is `docs/dev/macos-moltenvk-decision.md`. - [x] OpenGL depth-bounds submission now orders and clamps both values to the legal range before calling the driver, eliminating the two `GL_INVALID_VALUE` records that previously made `game/medlabs` the lone required-profile failure; its debug-context rerun is error-free. - [x] Shadow-map caster coverage and contact quality are corrected across the classic OpenGL, direct Vulkan, and shared interaction paths. Automatic culling now stores the near shell for sealed geometry and renders open/uncertain stock geometry two-sided; large point-light bias is world-bounded, balanced filter footprints are tighter and cheaper, PCSS blocker search is bias-consistent, map/alpha/dynamic cache ownership fails closed, projected and point resources carry exact world/pass/storage provenance through bind time, partial mapped ownerships receive exact stencil supplements or return to full stencil, Modern OpenGL refuses resources it cannot consume completely, atlas placement follows physical limits, and nested OpenGL elapsed queries no longer generate validation errors. Final staged, windowed OpenGL `game/airdefense1` stencil/mapped qualification passed 2/2 with retained engine captures under `.tmp/shadowmap-review/final-ab-runtime-6/`; stencil measured 96.8 Hz at p95 14 ms and mapped measured 74.8 Hz at p95 16 ms, while the mapped capture keeps the start-area buggy, crates, arches, and rails grounded without the former light-corner gap. A matching direct-Vulkan mapped smoke passed at 83.7 Hz and p95 15 ms with a clean capture and no Vulkan validation/call failure under `.tmp/shadowmap-review/final-vk-runtime-5/`. The low-frequency telemetry rerun under `.tmp/shadowmap-review/final-telemetry-6/` recorded 22 lights with six supported point lights, four local and four global mapped ownerships, 212 admitted casters, two successful translucent receiver chains, three complete fail-closed stencil fallbacks, 48 rendered point faces, zero unshadowed/render/mask/receiver failures, and 43 completed plus 21 still-pending asynchronous elapsed-time queries with zero synchronous waits, slot exhaustion, drops, or query failures. - [x] Known stock startup warnings are resolved without replacement content: the three brown-fluid images and large water splash omitted from the retail PK4s use fail-after-primary shipped-media fallbacks, generated TrueType atlas materials bind their already-uploaded intrinsic images on first parse and survive renderer restart, wide loading backgrounds retain atomic publication under long Windows save paths through a root-relative 128-bit CSPRNG staging identity, generated image caches fall back from the legacy qpath to a versioned SHA-256/128 compact identity without bypassing VFS or pure-server policy, and rigid-body states that are immediately clamped or forced to rest remain developer diagnostics while unrecovered physics faults keep warning severity. Every binary-image header, mip record, and payload write now rejects short output, and the renderer evidence tools fail closed on either cache-write or expanded-loadscreen-publication warnings. The final staged, windowed OpenGL `game/airdefense1` cold run and same-savepath warm reuse run passed with engine captures and no warning, error, fatal, or asset-load-failure records under `.tmp/unrelated-warning-fixes/airdefense1-compact-cache-cold-longpath-final-2/`: the fixture held the valid expanded TGA at 240 characters, reduced its formerly failing 263-character staging path to 222, forced nine overlong legacy cache identities onto compact paths, left no staging files behind, and preserved byte hashes plus modification times for all eight persistent stock/image-program compact caches on warm load. The generated loadscreen derivative alone refreshed as expected when its source TGA was atomically regenerated. +- [x] Muzzle flashes, bright projectiles, and explosions now light the world the way Quake II and Quake III did, through the new default-on `g_classicDynamicLights`. Firing raises a short, wide world flash at the barrel on top of the weapon's authored one; rockets, plasma bolts, dark matter spheres, and napalm carry their own light; and detonations flash the surrounding geometry, which no shipped Quake 4 explosion effect did at all. Colors are taken from the shipped assets rather than invented - muzzle flashes use each weapon def's own `flashColor`, and projectile and explosion colors come from the light segments Raven authored in `effects/weapons` and `effects/monsters` - so a classic light reads as part of the effect it belongs to. Projectiles whose def authors a light, and the single-player rocket and grenade whose shipped trail effects already carry a light segment, are left untouched; bullets, nails, debris, and glass are deliberately unlit. At most 24 one-shot flashes are alive at once, all unshadowed, distance-culled, and recycled oldest-first. `g_classicDynamicLightScale` scales their radius between 0.25 and 4, content can override or opt out per entity def with `classic_light`, `classic_light_color`, and `classic_light_radius`, and the cheat command `testClassicLight` raises them without firing a shot. - [ ] Finish Milestone A release qualification by repeating and retaining the full job, renderer-budget, retail-baseline, and human-review evidence from clean committed source and a freshly staged final package, then recording the required release platform/driver coverage. ## macOS Evidence Gate diff --git a/docs/user/classic-dynamic-lights.md b/docs/user/classic-dynamic-lights.md new file mode 100644 index 000000000..1ba04fdaa --- /dev/null +++ b/docs/user/classic-dynamic-lights.md @@ -0,0 +1,103 @@ +# Classic Dynamic Lights + +Quake II and Quake III lit the world from the action itself. Every muzzle flash threw a pool of light +across the room, every rocket and plasma ball carried its own glow down the corridor, and every +explosion flashed the walls for a tenth of a second. Quake 4 replaced that with authored BSE effects, +and most of the shipped effects carry no light at all: apart from the single-player rocket and +grenade trails, player projectiles are unlit, and **no explosion in the game flashes the room**. + +openQ4 adds that classic layer back. It is on by default. + +## Quick Reference + +| Setting | Default | Scope | What it does | +|---|---:|---|---| +| `g_classicDynamicLights` | `1` | SP and MP game code | Quake II/III style dynamic lights on muzzle flashes, bright projectiles and explosions. | +| `g_classicDynamicLightScale` | `1` | SP and MP game code | Radius multiplier for those lights, `0.25` to `4`. | + +Both are archived, so a change survives a restart. + +## What it adds + +**Muzzle flashes.** Firing raises a short, wide world light at the barrel — roughly 100 ms, and half +again the radius of the weapon's own authored flash. It is layered on top of the stock flash rather +than replacing it, so the shot reads as a punchier pop that briefly reaches further into the room. +Weapons that carry no flash light of their own get one from this. + +**Bright projectiles.** A rocket, plasma bolt or dark matter sphere carries a light with it. This +only fills gaps: a projectile whose def authors its own light (`mtr_light_shader`) keeps that light +untouched, and the single-player rocket and grenade — whose shipped trail effects already contain a +light segment — are left alone so their glow is not doubled. Their multiplayer trails carry no light +segment, so those do take the classic light. + +**Explosions.** A detonation flashes the surrounding geometry and fades over 120–400 ms depending on +what went off. This is entirely new: nothing in the shipped explosion effects lights the room. +Exploding barrels and damagables get it too. + +Bullets, nails, debris and glass deliberately get nothing. They are not bright, there are far too +many of them in flight to afford lighting them, and Quake II and Quake III did not light them either. + +## Colors + +The colors are taken from the shipped Quake 4 assets rather than invented, so a classic light reads +as part of the effect it belongs to instead of a wash of arbitrary color over it. + +Muzzle flashes use the firing weapon's own `flashColor`, whatever a mod sets it to. For stock +weapons that is: + +| Weapon | `flashColor` | Reads as | +|---|---|---| +| Blaster, Hyperblaster, Gauntlet | `0.7 0.8 1` | blue-white | +| Machinegun, Shotgun, Nailgun | `1 0.8 0.4` | warm white | +| Rocket Launcher, Grenade Launcher, Napalm Gun, Dark Matter Gun | `0.99 0.84 0.31` | amber | +| Railgun | `0.72 1 0.9` | pale green-white | +| Lightning Gun | `0.5 0.8 1` | pale blue | + +Projectile and explosion colors come from the light segments Raven did author elsewhere in the same +asset set: + +| Class | Color | Source | +|---|---|---| +| Rocket | `0.906 0.518 0.161` | `effects/weapons/rocketlauncher/fly.fx` | +| Grenade | `0.973 0.286 0.086` | `effects/weapons/grenadelauncher/trail.fx` | +| Napalm | `0.922 0.545 0.322` | `effects/weapons/napalmgun/globburn.fx` | +| Dark matter | `0.502 0 1` | `effects/weapons/dmg/core.fx` | +| Energy bolts | `0.7 0.8 1` | the energy weapons' own `flashColor` | +| Explosions | `1 0.824 0.290` | `effects/monsters/strogg_flyer/bomb_burst.fx` | + +## Content overrides + +An entity def can override the class the table picks for it: + +``` +"classic_light" "0" // never give this one a classic light +"classic_light_color" "1 0.5 0.2" // use this color instead of the table's +"classic_light_radius" "180" // tracking light radius, in units +``` + +`classic_light_radius` also overrides the "the shipped effect already lights this" rule, so a mod +that replaces the single-player rocket trail with an unlit one can ask for the classic light back. + +## Cost and tuning + +Each of these is a real light in this renderer, not the cheap vertex tint it was in Quake II. They +are all unshadowed, at most 24 one-shot flashes are alive at a time, flashes more than about 3000 +units from the viewer are skipped, and the oldest flash is recycled when the pool is full. + +If the effect is too strong for your taste, lower `g_classicDynamicLightScale` before turning the +feature off — `0.5` keeps the character at half the reach. Setting `g_classicDynamicLights 0` retires +every live flash immediately and stops new ones; projectiles already in flight keep the light they +were launched with until they expire. + +`g_projectileLights 0` still hides projectile lights, classic ones included, and `g_muzzleFlash 0` +still suppresses muzzle flashes. + +## Looking at them + +With cheats enabled, `testClassicLight` raises the flashes in front of you without firing a shot: + +``` +testClassicLight // one muzzle flash and one explosion +testClassicLight muzzle weapon_railgun // that weapon's flash color +testClassicLight explosion projectile_dmg // that projectile class's detonation flash +``` diff --git a/docs/user/gameplay-settings.md b/docs/user/gameplay-settings.md index c55ecd8bf..d9c7dd711 100644 --- a/docs/user/gameplay-settings.md +++ b/docs/user/gameplay-settings.md @@ -12,6 +12,8 @@ The auto-skip cinematics, corpse cleanup, and corpse sink controls are also avai | `g_corpseRemoveDelaySP` | `0` | Single-player | Controls how long SP corpses remain before disappearing. `0` uses stock timing, `-1` disables corpse removal. | | `g_corpseRemoveDelayMP` | `0` | Multiplayer | Controls how long MP corpses remain before disappearing. `0` uses stock timing, `-1` disables corpse removal. | | `g_corpseSink` | `0` | SP and MP game code | Selects corpse sink mode instead of the normal dissolve or burn-away behavior. | +| `g_classicDynamicLights` | `1` | SP and MP game code | Quake II/III style dynamic lights on muzzle flashes, bright projectiles and explosions. On by default. | +| `g_classicDynamicLightScale` | `1` | SP and MP game code | Radius multiplier for the classic dynamic lights, `0.25` to `4`. | | `s_musicVolume` | `0.5` | Client audio | Controls music volume independently of the main sound mix. | | `hud_damageNumbers` | `0` | Multiplayer client | Floating damage numbers over the players you hit. `0` off, `1` opponents only, `2` all damage you deal. | | `hud_damageNumberStyle` | `1` | Multiplayer client | How damage numbers are coloured. `1` white through red, `2` one colour per damage band, `3` one colour per weapon. | @@ -202,6 +204,19 @@ seta hud_damageNumberStyle 2 seta hud_damageNumberScale 1.25 ``` +## Classic Dynamic Lights + +`g_classicDynamicLights` restores the Quake II / Quake III habit of lighting the world from the +action: muzzle flashes throw a short, wide pool of light, bright projectiles carry their own glow, +and explosions flash the surrounding geometry. It is on by default, and the colors come from the +shipped Quake 4 assets. + +`g_classicDynamicLightScale` scales the radius of all of them between `0.25` and `4`. Lower it +before turning the feature off if the effect is stronger than you want. + +See [Classic Dynamic Lights](classic-dynamic-lights.md) for the per-weapon colors, the content +override keys, and the `testClassicLight` console command. + ## Example Presets ### Fast Testing Setup From 99c3c1a7f5a2c120a3cb00ad1567973474e803ae Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 3 Sep 2026 09:15:11 +0100 Subject: [PATCH 2/2] Move the CI GameLibs pin forward to the current game tree Both workflows pinned openQ4-game at 67fcd3e, which is now twelve commits behind. The engine's cross-repo tests assert the game tree's *current* contracts, so the pin drifting turns them into failures that have nothing to do with the commit under test: weapon_zoom_view_alignment has been red on main since the zoom-scope fix landed on the game side, because the pinned tree still calls UpdateZoomGuiViewState from the 2D overlay pass that the fix removed. Pin bfe7f45 instead, which is the game tree these workflows should have been validating all along, and which carries the ClassicLights work this branch documents. Verified by running the workflow's own 164-test list against the new pin: 158 pass, and the only six that do not are the ones that cannot run here at all - two require native Linux/macOS, three are invoked with required arguments in CI rather than bare, and one needs a built client in .install. Co-Authored-By: Claude Opus 5 --- .github/workflows/commit-validation.yml | 2 +- .github/workflows/push-verification.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/commit-validation.yml b/.github/workflows/commit-validation.yml index 782f9604a..cfa17eaad 100644 --- a/.github/workflows/commit-validation.yml +++ b/.github/workflows/commit-validation.yml @@ -15,7 +15,7 @@ env: # Keep the engine's shared File/FileSystem ABI locked to the companion # revision that implements GAME_API 46, generated-animation cache v3, and # the game-owned transient time-scale interface. - OPENQ4_GAMELIBS_SHA: 67fcd3e2142bc07c4871873ea1ec6fe592ac8a5a + OPENQ4_GAMELIBS_SHA: bfe7f45c1f7cf90ecf0ffd29b11e11b9265d860a jobs: script-smoke: diff --git a/.github/workflows/push-verification.yml b/.github/workflows/push-verification.yml index bc40344bc..755fc85ae 100644 --- a/.github/workflows/push-verification.yml +++ b/.github/workflows/push-verification.yml @@ -11,7 +11,7 @@ env: # Keep the engine's shared File/FileSystem ABI locked to the companion # revision that implements GAME_API 46, generated-animation cache v3, and # the game-owned transient time-scale interface. - OPENQ4_GAMELIBS_SHA: 67fcd3e2142bc07c4871873ea1ec6fe592ac8a5a + OPENQ4_GAMELIBS_SHA: bfe7f45c1f7cf90ecf0ffd29b11e11b9265d860a concurrency: group: openq4-push-verification-${{ github.ref }}