From b60ef82da62000ddf33a6ec51706ac5849994656 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 27 Aug 2026 00:49:36 +0100 Subject: [PATCH] Parse generated AAS 1.08 face settings --- .github/workflows/commit-validation.yml | 2 + .github/workflows/push-verification.yml | 2 + docs/dev/release-completion.md | 2 +- docs/dev/releases/v0.12.0.md | 1 + src/aas/AASFile.cpp | 12 ++-- tools/tests/aas_settings_contract.py | 83 +++++++++++++++++++++++++ tools/validation/openq4_validate.py | 1 + 7 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 tools/tests/aas_settings_contract.py diff --git a/.github/workflows/commit-validation.yml b/.github/workflows/commit-validation.yml index 030c474e..caf21e1d 100644 --- a/.github/workflows/commit-validation.yml +++ b/.github/workflows/commit-validation.yml @@ -80,6 +80,7 @@ jobs: tools/validation/openq4_validate.py \ tools/validation/stock_asset_baseline.py \ tools/tests/renderer_validation_matrix.py \ + tools/tests/aas_settings_contract.py \ tools/tests/arena_campaign.py \ tools/tests/async_drop_client_contract.py \ tools/tests/campaign_split_state_transition.py \ @@ -240,6 +241,7 @@ jobs: bash -n tools/validation/validate_push.sh bash -n tools/validation/validate_pr.sh python tools/validation/audit_source_provenance.py --check + python tools/tests/aas_settings_contract.py python tools/tests/arena_campaign.py python tools/tests/async_drop_client_contract.py python tools/tests/campaign_split_state_transition.py diff --git a/.github/workflows/push-verification.yml b/.github/workflows/push-verification.yml index 2b4d9328..55b5bcc9 100644 --- a/.github/workflows/push-verification.yml +++ b/.github/workflows/push-verification.yml @@ -80,6 +80,7 @@ jobs: tools/validation/openq4_validate.py \ tools/validation/stock_asset_baseline.py \ tools/tests/renderer_validation_matrix.py \ + tools/tests/aas_settings_contract.py \ tools/tests/arena_campaign.py \ tools/tests/async_drop_client_contract.py \ tools/tests/campaign_split_state_transition.py \ @@ -240,6 +241,7 @@ jobs: bash -n tools/validation/validate_push.sh bash -n tools/validation/validate_pr.sh python tools/validation/audit_source_provenance.py --check + python tools/tests/aas_settings_contract.py python tools/tests/arena_campaign.py python tools/tests/async_drop_client_contract.py python tools/tests/campaign_split_state_transition.py diff --git a/docs/dev/release-completion.md b/docs/dev/release-completion.md index fa3e0f65..7be2dd6b 100644 --- a/docs/dev/release-completion.md +++ b/docs/dev/release-completion.md @@ -892,7 +892,7 @@ is `docs/dev/macos-moltenvk-decision.md`. - [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] Retail and rebuilt AAS compatibility is restored: stock dummy `.aas` files load/discard like retail instead of warning-spamming and failing stock map init, AAS tactical data is cleared correctly between loads, and the AAS 1.08 settings reader now accepts the `generateAllFaces` key emitted by its own writer. The face/tactical builder flags are initialized and round-trip their selected values instead of being hardcoded on output. Issue #137's supplied `airdefense1` archive (`CED6E9D92E67D8FF71E1F2E93B68D7F6DD7C961D7E759E90E3AA243408AED187`) exposed the mismatch consistently in `.aas32`, `.aas48`, `.aas96`, `.aas128`, and `.aas250`. The focused source/CI contract passes, and a current 1280x720 windowed OpenGL run loaded the archive's exact `.map`, `.proc`, `.cm`, and all five AAS files from the isolated loose path, initialized gameplay, wrote the engine capture (`E62BD7DE995E16D31890CE9E44625FB829B20541DD7B7CB31729251214548F38`), and exited without an invalid-token, stale-AAS, navigation-load, engine error, or fatal diagnostic; the retained log is `74E5C6775C75CEA391E9E6D9E16443A026EFFAFDAC9291072F2966F50A73B196` under `.tmp/issue137-exact-map/`. - [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] 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. diff --git a/docs/dev/releases/v0.12.0.md b/docs/dev/releases/v0.12.0.md index 0569aef7..ee8e44ef 100644 --- a/docs/dev/releases/v0.12.0.md +++ b/docs/dev/releases/v0.12.0.md @@ -61,6 +61,7 @@ - Avoided constructing bake/debug-only fallback light-grid layouts during ordinary play on maps without baked grids; bake commands still generate the required layout on demand. - Corrected OpenGL and Vulkan shadow-map caster coverage with topology-aware face culling, near-shell depth for sealed geometry, two-sided depth for open geometry, bounded huge-light point bias, tighter 9-tap balanced filters, consistent PCSS blocker sampling, exact world/pass/storage cache identity, bind-time atlas and point-cube provenance, live point-format and projected-atlas generation validation, newest-compatible stale reuse that rejects moved or rescaled point lights, dynamic and partial-map stencil supplements, translucent-only GLOBAL ownership, hardware atlas bounds, and safely nested OpenGL GPU timing. - Added fail-after-primary compatibility fallbacks for the three stock brown-fluid images and large water-splash sample omitted from the retail PK4s, while preserving loose-file and mod overrides; generated TrueType atlas materials now use their uploaded intrinsic image identity from first parse and reinstall the full stage after renderer restart; generated wide loading backgrounds stage through a short CSPRNG-named root path before atomic publication; generated image caches retain their legacy identity first and use a deterministic compact identity only when a long save path cannot be opened, with every header, mip record, and payload write checked; expected rigid-body rest and inertia recovery remains developer-visible but no longer enters the warning stream. +- Rebuilt Quake 4 AAS 1.08 navigation files now load without `generateAllFaces` token warnings. The loader accepts both generated face/tactical-feature settings, initializes them deterministically, and the writer preserves their selected values. - Kept view-model client entities, attached animated models, and the lightning gun beam on the exact rendered skeletal pose. - Prevented remote-client prediction replays from advancing projectile visuals twice or replacing the viewweapon's authoritative interpolation endpoint, and restored impact/fizzle visuals when a short-lived projectile is first received in a terminal state. - Fixed weapon swaps that could inherit firing state or leave stale effects behind. diff --git a/src/aas/AASFile.cpp b/src/aas/AASFile.cpp index 38e3ff05..2c84965c 100644 --- a/src/aas/AASFile.cpp +++ b/src/aas/AASFile.cpp @@ -161,6 +161,7 @@ idAASSettings::idAASSettings( void ) { boundingBoxes[0] = idBounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); usePatches = false; // jmarshall - aas 1.08 + generateAllFaces = false; generateTacticalFeatures = false; // jmarshall end writeBrushMap = false; @@ -296,8 +297,11 @@ bool idAASSettings::FromParser( idLexer &src ) { if ( !ParseBool( src, usePatches ) ) { return false; } } // jmarshall: AAS 1.08 - else if (token == "generateTacticalFeatures") { - if (!ParseBool(src, generateTacticalFeatures)) { return false; } + else if ( token == "generateAllFaces" ) { + if ( !ParseBool( src, generateAllFaces ) ) { return false; } + } + else if ( token == "generateTacticalFeatures" ) { + if ( !ParseBool( src, generateTacticalFeatures ) ) { return false; } } // jmarshall end else if ( token == "writeBrushMap" ) { @@ -503,8 +507,8 @@ bool idAASSettings::WriteToFile( idFile *fp ) const { fp->WriteFloatString( "\tallowSwimReachabilities = %d\n", allowSwimReachabilities ); fp->WriteFloatString( "\tallowFlyReachabilities = %d\n", allowFlyReachabilities ); // jmarshall - AAS 1.08 - fp->WriteFloatString("\tgenerateAllFaces = 0\n"); - fp->WriteFloatString("\tgenerateTacticalFeatures = 0\n"); + fp->WriteFloatString( "\tgenerateAllFaces = %d\n", generateAllFaces ); + fp->WriteFloatString( "\tgenerateTacticalFeatures = %d\n", generateTacticalFeatures ); // jmarshall end fp->WriteFloatString( "\tfileExtension = \"%s\"\n", fileExtension.c_str() ); fp->WriteFloatString( "\tgravity = (%f %f %f)\n", gravity.x, gravity.y, gravity.z ); diff --git a/tools/tests/aas_settings_contract.py b/tools/tests/aas_settings_contract.py new file mode 100644 index 00000000..73ee9647 --- /dev/null +++ b/tools/tests/aas_settings_contract.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Guard the Quake 4 AAS 1.08 settings parser/writer round trip.""" + +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +TEST_PATH = "tools/tests/aas_settings_contract.py" + + +def read(relative_path: str) -> str: + return (ROOT / relative_path).read_bytes().decode("windows-1252") + + +def function_body(source: str, signature: str, context: str) -> str: + start = source.find(signature) + if start < 0: + raise AssertionError(f"Missing {signature!r} in {context}") + opening = source.find("{", start + len(signature)) + if opening < 0: + raise AssertionError(f"Missing body for {signature!r} in {context}") + + depth = 0 + for index in range(opening, len(source)): + if source[index] == "{": + depth += 1 + elif source[index] == "}": + depth -= 1 + if depth == 0: + return source[opening + 1 : index] + raise AssertionError(f"Unterminated body for {signature!r} in {context}") + + +def require(source: str, token: str, context: str) -> None: + if token not in source: + raise AssertionError(f"Missing {token!r} in {context}") + + +def validate_aas_settings_round_trip() -> None: + source = read("src/aas/AASFile.cpp") + + constructor = function_body(source, "idAASSettings::idAASSettings( void )", "AAS settings constructor") + require(constructor, "generateAllFaces = false;", "AAS settings constructor") + require(constructor, "generateTacticalFeatures = false;", "AAS settings constructor") + + parser = function_body(source, "bool idAASSettings::FromParser( idLexer &src )", "AAS settings parser") + require(parser, 'token == "generateAllFaces"', "AAS 1.08 settings parser") + require(parser, "ParseBool( src, generateAllFaces )", "AAS 1.08 settings parser") + require(parser, 'token == "generateTacticalFeatures"', "AAS 1.08 settings parser") + require(parser, "ParseBool( src, generateTacticalFeatures )", "AAS 1.08 settings parser") + + writer = function_body(source, "bool idAASSettings::WriteToFile( idFile *fp ) const", "AAS settings writer") + for setting in ("generateAllFaces", "generateTacticalFeatures"): + require(writer, f'"\\t{setting} = %d\\n", {setting}', "AAS 1.08 settings writer") + if f'"\\t{setting} = 0\\n"' in writer: + raise AssertionError(f"AAS settings writer still hardcodes {setting}") + + +def validate_ci_wiring() -> None: + validator = read("tools/validation/openq4_validate.py") + if validator.count("aas_settings_contract.py") != 1: + raise AssertionError("Local validation must register the AAS settings contract exactly once") + + for workflow_path in ( + ".github/workflows/commit-validation.yml", + ".github/workflows/push-verification.yml", + ): + workflow = read(workflow_path) + if workflow.count(TEST_PATH) != 2: + raise AssertionError(f"{workflow_path} must compile and run {TEST_PATH}") + require(workflow, f"python {TEST_PATH}", workflow_path) + + +def main() -> None: + validate_aas_settings_round_trip() + validate_ci_wiring() + print("aas_settings_contract: ok") + + +if __name__ == "__main__": + main() diff --git a/tools/validation/openq4_validate.py b/tools/validation/openq4_validate.py index 059fea42..fad33f1a 100644 --- a/tools/validation/openq4_validate.py +++ b/tools/validation/openq4_validate.py @@ -404,6 +404,7 @@ def validate_game_libs_repo_path(game_libs_repo: Path) -> Path: def run_python_tests(args: argparse.Namespace, root: Path, env: dict[str, str]) -> None: tests = [ + root / "tools" / "tests" / "aas_settings_contract.py", root / "tools" / "tests" / "arena_campaign.py", root / "tools" / "tests" / "async_drop_client_contract.py", root / "tools" / "tests" / "campaign_split_state_transition.py",