From 47dd0e1c620bfede6ab6e1914d9983d23d7ae5e5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 11 Mar 2026 15:45:14 +0100 Subject: [PATCH 001/372] doc: sub agent parallel work + only use sub agents for simple tasks --- AGENTS.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fa020e417..7a64c0ddd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -207,15 +207,17 @@ Do not batch up multiple percentage milestones into one commit — commit as eac ## Parallel Sub-Agent Matching -When working on a translation unit with multiple non-matching functions, you are encouraged to spawn sub-agents to work on individual functions in parallel. Each sub-agent should focus on **exactly one function** — do not assign a sub-agent more than one function at a time. +When working on a translation unit with multiple non-matching functions, use sub-agents selectively for **simple, small, isolated** functions. The main agent should keep ownership of the harder matching work instead of delegating it away. Each sub-agent should focus on **exactly one function** — do not assign a sub-agent more than one function at a time. **Limit: never run more than 5 sub-agents concurrently.** Spawning too many at once causes resource contention and makes it harder to reason about progress. Guidelines: -- Spawn a sub-agent per function for functions that are independent (no shared edits to the same source lines). +- Prefer solving difficult, high-risk, or cross-cutting functions yourself. Use sub-agents only for straightforward functions with small, well-bounded edits. +- Spawn a sub-agent per function only when the functions are independent (no shared edits to the same source lines). - Each sub-agent must use `build-unit.py` for parallel-safe compilation (never plain `ninja`). -- Wait for a batch of sub-agents to finish before spawning the next batch. -- After all sub-agents in a batch complete, check the updated match percentage and commit if it improved. +- Do **not** sit idle waiting for sub-agents to finish. While they run, continue investigating or implementing other independent work in parallel. +- Before applying a sub-agent's result, re-read the touched area and make sure it still fits the current state of the TU. +- After a useful sub-agent result lands, check the updated match percentage and commit if it improved. ## Matching Philosophy From 4ff6a3f6fcfc3adc73abcc7497e984fb122eae0e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 11 Mar 2026 19:54:50 +0100 Subject: [PATCH 002/372] Update shared worktree tooling and docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/execute/SKILL.md | 112 ++++------ .github/skills/implement/SKILL.md | 2 +- AGENTS.md | 54 +++-- README.md | 22 ++ tools/build-unit.py | 142 +++++++++--- tools/decomp-context.py | 6 +- tools/share_worktree_assets.py | 351 ++++++++++++++++++++++++++++++ 7 files changed, 564 insertions(+), 125 deletions(-) create mode 100644 tools/share_worktree_assets.py diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 6b0d78ce0..b82c8b223 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -5,23 +5,25 @@ description: Workflow for decompiling an entire translation unit end-to-end. # Translation Unit Execution Workflow -Your goal is to orchestrate reverse engineering, class scaffolding, and function-by-function -matching to produce C++ source that compiles to byte-identical object code against the -original retail binary. +Your goal is to decompile a full translation unit: understand the current state, +scaffold any missing classes if needed, then match the unit function by function until +the produced C++ compiles to byte-identical object code against the original retail binary. ## Overview -This skill coordinates several agent types: +This workflow combines several smaller workflows: -1. **reverse-engineer** — Update Ghidra with accurate data types for the class -2. **scaffolder** — Create header/source if the class is not yet in the project (see `.github/skills/scaffold/SKILL.md`) -3. **implementer** — Match each function one at a time until the TU is complete (see `.github/skills/implement/SKILL.md`) -4. **refiner** — Use on non-matching functions to improve the match. Applies systematic lateral strategies for stubborn mismatches (see `.github/skills/refiner/SKILL.md`). +1. **Scaffold** missing classes or headers when the TU depends on types that do not yet exist (see `.github/skills/scaffold/SKILL.md`). +2. **Implement** each missing or nonmatching function one at a time (see `.github/skills/implement/SKILL.md`). +3. **Refine** stubborn 80–99% functions after the obvious implementation has already been tried (see `.github/skills/refiner/SKILL.md`). -All non-read-only work is done **sequentially** — never spawn multiple writing agents at -the same time, as they will interfere with each other. +Work through the TU **sequentially** and keep one coherent state in the source tree. -**Avoid** doing deep dives into Ghidra or the assembly yourself — instead, rely on the agents to gather and analyze that context. Your context window is precious, so focus on high-level orchestration, monitoring progress, and providing agents the necessary information they need to do their work. +You may use sub-agents for **read-only reconnaissance only**: symbol searches, Ghidra +inspection, dump lookups, line mapping, assembly review, or summarizing the current +state of a TU. Sub-agents must **not** write or edit repository files. All scaffolding, +implementation, refactoring, and other persistent file changes must be done directly by +the main worker after reviewing the read-only findings. ## Phase 0: Establish Baseline @@ -32,13 +34,17 @@ ninja # ensure clean build ninja baseline # snapshot current match state ``` -All agents that modify code should check `ninja changes` after modifying shared headers -to verify no regressions were introduced. An empty changeset means no regressions. If -regressions appear, the shared header change must be reverted. +After modifying shared headers, check `ninja changes` to verify no regressions were +introduced. An empty changeset means no regressions. If regressions appear, the shared +header change must be reverted. ## Phase 1: Reconnaissance -Before spawning any implementation agents, understand the current state of the TU. +Before making changes, understand the current state of the TU. + +This phase is a good fit for read-only sub-agents. They can gather function lists, inspect +Ghidra output, trace line mappings, and summarize missing/nonmatching areas, but they must +not edit files or apply code changes. ### 1a. Identify the file path @@ -55,12 +61,10 @@ nonmatching, and matching functions. ## Phase 2: Scaffold (if needed) -A jump file contains many files and classes. Spawn a `scaffolder` -agent to create each class whose definition does not yet exist in the project. The scaffolder will: - -- Create the structs in the correct header files with accurate class layouts from the dwarf - -Wait for this agent to complete before proceeding. +A jump file contains many files and classes. If the TU depends on a type whose +definition does not yet exist in the project, follow the scaffold workflow in +`.github/skills/scaffold/SKILL.md` to create the needed header/source definitions +before moving on. ## Phase 3: Implement Functions @@ -68,7 +72,7 @@ Wait for this agent to complete before proceeding. After scaffolding, rebuild and re-check the function list. Use `build-unit.py` to compile to a private temp `.o` so the status check isn't -polluted by another parallel agent's compilation: +polluted by another concurrent temp build: ```sh ninja # full build to update shared state (progress, sha1) @@ -79,51 +83,40 @@ python tools/decomp-diff.py -u main/Path/To/TU -s missing -t function --base-obj ### 3c. Implement each function sequentially -For each missing or nonmatching function, spawn an `implementer` agent. Provide: - -- The class name and function name -- The TU path -- Any context from previous iterations (e.g. patterns discovered, field types clarified) -- Accumulated matching tips from previous agents (see below) +For each missing or nonmatching function, follow the implementation workflow in +`.github/skills/implement/SKILL.md`. **Important considerations:** -- **One at a time.** Never spawn multiple implementer agents concurrently. +- **One at a time.** Keep the tree in a coherent state as you work through the list. - **Balance new vs fixing.** Don't get stuck on one stubborn function — sometimes implementing the next function reveals patterns that make the previous one click. - But don't leave too many functions nonmatching, as agents may copy incorrect patterns. - **Mismatch triage:** - `@stringBase0` offset mismatches often resolve as more string literals are added - Register swaps and stack layout issues require direct intervention - Branch structure mismatches indicate wrong control flow (if/switch/loop) - **Match percentage is misleading.** The last few percent are often the hardest. - Agents may assume a 95% match is "close enough" — remind them that the goal is 100%. + Treat 95% as unfinished; the goal is 100%. ### 3d. Collect and propagate matching tips -Every implementer agent prompt should include: - -- All matching tips accumulated so far from previous agents in this session -- A request to **report any new assembly patterns or matching tips** discovered - -After each agent completes, evaluate its reported tips: +Keep notes on useful patterns you discover while working through the TU. +After each useful result, evaluate whether the pattern is generalizable: - **Generalizable patterns** (e.g. `fmuls fX, fX, fY` == `*=`) should be added to AGENTS.md's "Assembly patterns" section so all future sessions benefit. - **TU-specific patterns** (e.g. "this class uses `const char*` cast for bool array - access") should be kept in the session context and passed to subsequent agents but + access") should be kept in the session context and applied to subsequent functions but not added to AGENTS.md. ### 3f. Regression checking -Remind agents in their prompts: +After modifying any shared headers, run `ninja changes` to check for regressions. +Empty changeset = no regressions. If regressions appear, revert the shared change +and use a local workaround instead. -> After modifying any shared headers, run `ninja changes` to check for regressions. -> Empty changeset = no regressions. If regressions appear, revert the shared change -> and use a local workaround instead. - -> Use `build-unit.py` + `--base-obj` for all diff and context commands so your -> results are isolated from other agents compiling the same TU concurrently. +Use `build-unit.py` + `--base-obj` for diff and context commands when you want +results isolated from other concurrent builds of the same TU. ### 3g. Periodic reassessment @@ -154,8 +147,8 @@ python tools/decomp-diff.py -u main/Path/To/TU -s nonmatching ninja changes ``` -For any remaining nonmatching functions, make one final pass with the implementer agent, -providing all context accumulated during the session. +For any remaining nonmatching functions, make one final pass using the implementation +or refiner workflow with all context accumulated during the session. ## Phase 5: Report @@ -167,28 +160,3 @@ Summarize the session: - **Matching tips** — new assembly patterns discovered (note which are generalizable) - **Adjacent classes touched** — any scaffolding/RE done on related classes - **Recommendations** — what to tackle next, dependencies on other TUs - -## Agent Prompt Template - -When spawning implementer agents, always include these standard instructions: - -``` -Source file: src/[PathToClass].cpp -Header: include/[PathToClass].hpp -ASM: build/GOWE69/asm/[PathToClass].s - -Implement the function [ClassName]::[FunctionName] - -**Standard agent instructions:** -- Use the lookup and line-lookup skills for dwarf info. -- After modifying shared headers, run `ninja changes` to check for regressions (empty = good). -- Use `build-unit.py` + `--base-obj` for all build/diff/context/dwarf-dump commands so your - compiled output is isolated from other agents working on different TUs: - TEMPOBJ=$(python tools/build-unit.py -u main/Path/To/TU) - python tools/decomp-diff.py -u main/Path/To/TU -d [FunctionName] --base-obj "$TEMPOBJ" - dtk dwarf dump "$TEMPOBJ" -o /tmp/TU_check.nothpp -- Report any new general assembly patterns or matching tips you discover. - -**Matching tips from this session:** -[accumulated tips here] -``` diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 65b2ddd2a..52f30fefb 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -89,7 +89,7 @@ The game uses stlport, so you'll often encounter \_STL, but in the code it must ### Initial build -Compile to a private temp `.o` so your output isn't overwritten by other parallel agents: +Compile to a private temp `.o` so your output isn't overwritten by other concurrent builds: ```sh TEMPOBJ=$(python tools/build-unit.py -u main/Path/To/TU) diff --git a/AGENTS.md b/AGENTS.md index 7a64c0ddd..cdf83bc28 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,19 @@ objdiff.json Generated build/diff configuration ## Agent Tooling +## Sub-Agent Usage + +Sub-agents are allowed only for **read-only exploration** tasks such as: + +- searching the codebase for symbols, call sites, or include relationships +- inspecting decomp output, assembly, DWARF, PS2 dumps, or line mappings +- gathering context from Ghidra, `lookup.py`, `decomp-diff.py`, or similar tools +- summarizing findings that help the main worker decide what to change + +Sub-agents must **not** write or edit code files, headers, configs, or other repository files. +All persistent file changes, decomp implementations, scaffolding, and follow-up fixes must be +done by the main worker after reviewing the read-only findings. + ### lookup.py — Symbol lookup from the debug dump Query structs, enums, functions, globals, and typedefs directly from the pre-extracted @@ -66,8 +79,8 @@ python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -d FindIOWin - Mismatched args are wrapped in `{}`. Matching runs are collapsed (control with `-C ` context lines, `--no-collapse`). Left = original, right = decomp. -**Parallel-safe usage** — when multiple agents compile the same TU, pass a private `--base-obj` -so each agent diffs against its own compiled output and they never interfere: +**Parallel-safe usage** — when you compile the same TU in multiple concurrent iterations, +pass a private `--base-obj` so each diff uses its own compiled output: ```sh TEMPOBJ=$(python tools/build-unit.py -u main/Speed/Indep/SourceLists/zAnim) @@ -135,7 +148,7 @@ If it finds a match, include that header instead of redeclaring. Dump the dwarf of your own implementation of a function. **Always use the temp `.o` produced by `build-unit.py`** so the dump reflects your own -compilation and isn't overwritten by another parallel agent: +compilation and isn't overwritten by another concurrent temp build: ```sh TEMPOBJ=$(python tools/build-unit.py -u main/Speed/Indep/SourceLists/UNITNAME) @@ -151,7 +164,7 @@ dtk demangle 'AcceptScriptMsg__7CEntityF20EScriptObjectMessage9TUniqueIdR13CStat ### build-unit.py — Parallel-safe compilation Compile a single translation unit to a private temporary `.o` file that won't be -overwritten by other agents. Always prefer this over plain `ninja` when you need to +overwritten by other concurrent temp builds. Always prefer this over plain `ninja` when you need to diff or inspect your own compiled output: ```sh @@ -171,6 +184,21 @@ python tools/decomp-context.py -u main/Path/To/TU --base-obj "$TEMPOBJ" -f Fun dtk dwarf dump "$TEMPOBJ" -o /tmp/TU_check.nothpp ``` +### share_worktree_assets.py — Share stable assets across git worktrees + +Deduplicate immutable debug inputs and downloaded tool binaries across all git +worktrees while keeping per-worktree generated build files local: + +```sh +python tools/share_worktree_assets.py link --all +python tools/share_worktree_assets.py status --all +``` + +This shares extracted `orig/*` contents, `symbols/*`, root ELF / MAP files, and +downloaded tool binaries under `build/`. It does **not** share `build.ninja`, +`objdiff.json`, `compile_commands.json`, or per-worktree object outputs, so run +`python configure.py` inside each worktree after linking. + ## Code Conventions This is a **C++98** codebase compiled with ProDG (GCC under the hood). Key rules: @@ -205,24 +233,14 @@ Examples: Do not batch up multiple percentage milestones into one commit — commit as each improvement lands. -## Parallel Sub-Agent Matching - -When working on a translation unit with multiple non-matching functions, use sub-agents selectively for **simple, small, isolated** functions. The main agent should keep ownership of the harder matching work instead of delegating it away. Each sub-agent should focus on **exactly one function** — do not assign a sub-agent more than one function at a time. - -**Limit: never run more than 5 sub-agents concurrently.** Spawning too many at once causes resource contention and makes it harder to reason about progress. - -Guidelines: -- Prefer solving difficult, high-risk, or cross-cutting functions yourself. Use sub-agents only for straightforward functions with small, well-bounded edits. -- Spawn a sub-agent per function only when the functions are independent (no shared edits to the same source lines). -- Each sub-agent must use `build-unit.py` for parallel-safe compilation (never plain `ninja`). -- Do **not** sit idle waiting for sub-agents to finish. While they run, continue investigating or implementing other independent work in parallel. -- Before applying a sub-agent's result, re-read the touched area and make sure it still fits the current state of the TU. -- After a useful sub-agent result lands, check the updated match percentage and commit if it improved. - ## Matching Philosophy You should take the Ghidra decompiler output for the initial translation step, get it to compile, make sure that the dwarf of the function matches and only then look for binary matching problems in the assembly. Be aware Ghidra usually gets the order of branches incorrect in if statements (it inverts the logic and the two bodies are swapped), this needs to be fixed to achieve bytematching status. +You may use sub-agents to gather read-only context during this process, but they must not +edit files. Treat their output as analysis input for the main worker, not as a path to +delegate source changes. + The dwarf of your structs doesn't have to neccessarily match the original due to various reasons, just make sure that you copied everything correctly. Never dismiss a diff as "close enough" or "just register allocation." Every mismatched diff --git a/README.md b/README.md index 744c8c0af..b74f298fd 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,28 @@ sudo xattr -rd com.apple.quarantine '/Applications/Wine Crossover.app' - PS2: Copy `NSF.ELF` to `./orig/SLES-53558-A124/` +- Sharing large assets across git worktrees + + If you use multiple git worktrees, you can deduplicate the large immutable inputs + and downloaded tool binaries while keeping each worktree's generated build files + separate: + + ```sh + python tools/share_worktree_assets.py link --all + ``` + + This shares the ignored debug/tool assets under the git common directory, including + extracted `orig/*` contents, `symbols/*`, root ELF / MAP files, and downloaded + tool binaries under `build/`. It intentionally does **not** share `build.ninja`, + `objdiff.json`, `compile_commands.json`, or per-worktree object outputs. + + After linking shared assets into a worktree, regenerate that worktree's local build + files with: + + ```sh + python configure.py + ``` + # Diffing Once the initial build succeeds, an `objdiff.json` should exist in the project root. diff --git a/tools/build-unit.py b/tools/build-unit.py index cdc78875b..dbfacf7b7 100644 --- a/tools/build-unit.py +++ b/tools/build-unit.py @@ -29,12 +29,15 @@ import subprocess import sys import tempfile -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple, Union script_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = os.path.abspath(os.path.join(script_dir, "..")) OBJDIFF_JSON = os.path.join(root_dir, "objdiff.json") BUILD_NINJA = os.path.join(root_dir, "build.ninja") +COMPILE_COMMANDS = os.path.join(root_dir, "compile_commands.json") + +Command = Union[str, List[str]] def load_objdiff() -> Dict[str, Any]: @@ -51,8 +54,24 @@ def find_unit_source(config: Dict[str, Any], unit_name: str) -> Optional[str]: return None +def find_unit_target(config: Dict[str, Any], unit_name: str) -> Optional[str]: + """Return the build target path for a unit from objdiff.json, or None.""" + for unit in config.get("units", []): + if unit["name"] == unit_name: + target = unit.get("base_path") or unit.get("target_path") + return str(target) if target else None + return None + + def get_compdb() -> Optional[List[Dict[str, Any]]]: - """Run `ninja -t compdb` and return the parsed compilation database.""" + """Load compile_commands.json, falling back to `ninja -t compdb` if needed.""" + if os.path.exists(COMPILE_COMMANDS): + try: + with open(COMPILE_COMMANDS) as f: + return json.load(f) + except json.JSONDecodeError as e: + print(f"Failed to parse compile_commands.json: {e}", file=sys.stderr) + result = subprocess.run( ["ninja", "-t", "compdb"], capture_output=True, @@ -71,27 +90,72 @@ def get_compdb() -> Optional[List[Dict[str, Any]]]: return None +def get_build_command(target_path: str) -> Optional[str]: + """Return the final ninja command used to build target_path.""" + result = subprocess.run( + ["ninja", "-t", "commands", target_path], + capture_output=True, + cwd=root_dir, + ) + if result.returncode != 0: + print( + f"ninja -t commands failed:\n{result.stderr.decode(errors='replace')}", + file=sys.stderr, + ) + return None + + commands = [line.strip() for line in result.stdout.decode().splitlines() if line.strip()] + return commands[-1] if commands else None + + def find_entry( compdb: List[Dict[str, Any]], source_path: str ) -> Optional[Dict[str, Any]]: - """Find the compdb entry whose 'file' matches source_path.""" + """Find the compdb entry whose 'file' matches source_path. + + Prefers entries whose output is a .o file (actual compiler invocations) + over auxiliary entries (e.g. hash generation). + """ abs_source = os.path.normcase(os.path.abspath(os.path.join(root_dir, source_path))) + candidates = [] for entry in compdb: file_val = entry.get("file", "") if not os.path.isabs(file_val): entry_dir = entry.get("directory", root_dir) file_val = os.path.abspath(os.path.join(entry_dir, file_val)) if os.path.normcase(file_val) == abs_source: + candidates.append(entry) + for entry in candidates: + out = entry.get("output", "") + if out.endswith(".o") or out.endswith(".obj"): return entry - return None + return candidates[0] if candidates else None + + +def get_command(entry: Dict[str, Any]) -> Command: + command = entry.get("command") + if isinstance(command, str): + return command + arguments = entry.get("arguments") + if isinstance(arguments, list): + return arguments[:] -def strip_transform_dep(command: str) -> str: + print( + "Compilation entry is missing both 'command' and 'arguments'", + file=sys.stderr, + ) + sys.exit(1) + + +def strip_transform_dep(command: Command) -> Command: """Remove the `&& python transform_dep.py ...` step from a compile command. The dependency file transformation is only needed for incremental ninja builds; it is safe to skip for one-off temp compilations. """ + if isinstance(command, list): + return command return re.sub( r"\s*&&\s*\S*python3?\S*\s+\S*transform_dep\.py\s+\S+\s+\S+", "", @@ -99,43 +163,58 @@ def strip_transform_dep(command: str) -> str: ) -def redirect_output(command: str, source_path: str, new_output: str) -> str: +def find_output_argument(command: Command) -> Optional[Tuple[int, str]]: + if isinstance(command, list): + for i in range(len(command) - 1): + if command[i] == "-o": + return i + 1, command[i + 1] + return None + + m = re.search(r"(? Command: """Replace the compiler output path in command with new_output. Handles two styles: - Direct file output (-o path/to/file.o): ngccc/ProDG, MSVC, EE-GCC - Directory output (-o path/to/dir): mwcc (MWCC outputs to a dir) """ - m = re.search(r"(?/.o automatically. - new_basedir = os.path.dirname(new_output) - return command[: m.start(1)] + new_basedir + command[m.end(1) :] + replacement = os.path.dirname(new_output) + if isinstance(command, list): + new_command = command[:] + new_command[index] = replacement + return new_command -def actual_output_path(command: str, source_path: str, new_output: str) -> str: + return command[:index] + replacement + command[index + len(o_arg) :] + + +def actual_output_path(command: Command, source_path: str, new_output: str) -> str: """Return the path where the compiled .o actually lands. For direct-file compilers this is new_output. For directory-output compilers it is /.o. """ - m = re.search(r"(? str: config = load_objdiff() source_path = find_unit_source(config, unit_name) + target_path = find_unit_target(config, unit_name) if not source_path: print( f"No source_path found for unit '{unit_name}' in objdiff.json.\n" @@ -158,6 +238,12 @@ def compile_unit(unit_name: str, output_path: str) -> str: file=sys.stderr, ) sys.exit(1) + if not target_path: + print( + f"No target_path found for unit '{unit_name}' in objdiff.json.", + file=sys.stderr, + ) + sys.exit(1) if not os.path.exists(BUILD_NINJA): print( @@ -166,21 +252,15 @@ def compile_unit(unit_name: str, output_path: str) -> str: ) sys.exit(1) - compdb = get_compdb() - if compdb is None: - sys.exit(1) - - entry = find_entry(compdb, source_path) - if entry is None: + command = get_build_command(target_path) + if command is None: print( - f"No compilation entry found for '{source_path}'.\n" - "Make sure the source file exists and `ninja all_source` has been run.", + f"No build command found for target '{target_path}'.\n" + "Make sure the unit exists and `python configure.py` has been run.", file=sys.stderr, ) sys.exit(1) - command = entry["command"] - # 1. Strip the dependency-file transform step — not needed for temp builds. command = strip_transform_dep(command) @@ -196,7 +276,7 @@ def compile_unit(unit_name: str, output_path: str) -> str: command = redirect_output(command, source_path, output_path) # 5. Run the compile. - result = subprocess.run(command, shell=True, cwd=root_dir) + result = subprocess.run(command, shell=isinstance(command, str), cwd=root_dir) if result.returncode != 0: print( f"Compilation failed (exit code {result.returncode})", file=sys.stderr diff --git a/tools/decomp-context.py b/tools/decomp-context.py index de8bafc0a..0ced57453 100644 --- a/tools/decomp-context.py +++ b/tools/decomp-context.py @@ -260,7 +260,7 @@ def check_ghidra() -> None: # Try a minimal command that lists available programs try: result = subprocess.run( - [ghidra_cmd, "list", "programs"], + [ghidra_cmd, "program", "list"], capture_output=True, timeout=15, ) @@ -275,9 +275,9 @@ def check_ghidra() -> None: print(f" Run: ghidra set-default project NeedForSpeed") if result.returncode != 0 and stderr: - print(f"WARN ghidra list programs exited {result.returncode}: {stderr}") + print(f"WARN ghidra program list exited {result.returncode}: {stderr}") except subprocess.TimeoutExpired: - print("WARN ghidra list programs timed out — Ghidra may be slow to start") + print("WARN ghidra program list timed out — Ghidra may be slow to start") except Exception as e: print(f"WARN could not verify programs: {e}") diff --git a/tools/share_worktree_assets.py b/tools/share_worktree_assets.py new file mode 100644 index 000000000..542f4b407 --- /dev/null +++ b/tools/share_worktree_assets.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python3 + +""" +Share stable debug/tool assets across git worktrees. + +This keeps branch-specific generated files (`build.ninja`, `objdiff.json`, +`compile_commands.json`, object files, etc.) local to each worktree while +deduplicating large immutable assets such as extracted game files, symbol dumps, +and downloaded tool binaries under the git common directory. + +Examples: + python tools/share_worktree_assets.py status + python tools/share_worktree_assets.py status --all + python tools/share_worktree_assets.py link --all +""" + +import argparse +import filecmp +import os +import shutil +import subprocess +import sys +from dataclasses import dataclass +from typing import Dict, Iterable, List, Optional, Set + +script_dir = os.path.dirname(os.path.realpath(__file__)) +root_dir = os.path.abspath(os.path.join(script_dir, "..")) + +SHARED_ROOT_NAME = "worktree-shared" + + +@dataclass(frozen=True) +class AssetSpec: + relpath: str + kind: str + + +FIXED_ASSETS = ( + AssetSpec("NFSMWRELEASE.ELF", "file"), + AssetSpec("NFS.ELF", "file"), + AssetSpec("NFS.MAP", "file"), + AssetSpec(os.path.join("build", "tools"), "dir"), + AssetSpec(os.path.join("build", "compilers"), "dir"), + AssetSpec(os.path.join("build", "ppc_binutils"), "dir"), +) + + +def run_git(args: List[str], cwd: str) -> str: + result = subprocess.run( + ["git", *args], + cwd=cwd, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(result.stderr.strip(), file=sys.stderr) + sys.exit(result.returncode) + return result.stdout + + +def git_common_dir(cwd: str) -> str: + common = run_git(["rev-parse", "--git-common-dir"], cwd).strip() + if os.path.isabs(common): + return common + return os.path.abspath(os.path.join(cwd, common)) + + +def list_worktrees(cwd: str) -> List[str]: + output = run_git(["worktree", "list", "--porcelain"], cwd) + worktrees = [] + for line in output.splitlines(): + if line.startswith("worktree "): + worktrees.append(line.split(" ", 1)[1]) + return worktrees + + +def tracked_paths(cwd: str) -> Set[str]: + output = run_git(["ls-files"], cwd) + return {line.strip() for line in output.splitlines() if line.strip()} + + +def lexists(path: str) -> bool: + return os.path.lexists(path) + + +def same_symlink(path: str, target: str) -> bool: + return os.path.islink(path) and os.path.realpath(path) == os.path.realpath(target) + + +def remove_path(path: str) -> None: + if os.path.islink(path) or os.path.isfile(path): + os.unlink(path) + elif os.path.isdir(path): + shutil.rmtree(path) + + +def ensure_parent(path: str) -> None: + parent = os.path.dirname(path) + if parent: + os.makedirs(parent, exist_ok=True) + + +def merge_file(src: str, dst: str, relpath: str) -> None: + ensure_parent(dst) + if not os.path.exists(dst): + shutil.copy2(src, dst) + return + if not filecmp.cmp(src, dst, shallow=False): + raise RuntimeError(f"Conflicting file contents for {relpath}") + + +def merge_symlink(src: str, dst: str, relpath: str) -> None: + resolved = os.path.realpath(src) + if os.path.isdir(resolved): + merge_tree(resolved, dst, relpath) + elif os.path.isfile(resolved): + merge_file(resolved, dst, relpath) + else: + raise RuntimeError(f"Broken symlink encountered while merging {relpath}: {src}") + + +def merge_tree(src: str, dst: str, relpath: str) -> None: + for current_root, dirnames, filenames in os.walk(src): + dirnames.sort() + filenames.sort() + rel_dir = os.path.relpath(current_root, src) + target_root = dst if rel_dir == "." else os.path.join(dst, rel_dir) + os.makedirs(target_root, exist_ok=True) + + next_dirnames = [] + for dirname in dirnames: + src_dir = os.path.join(current_root, dirname) + if os.path.islink(src_dir): + dst_dir = os.path.join(target_root, dirname) + rel_entry = os.path.join(relpath, os.path.relpath(src_dir, src)) + merge_symlink(src_dir, dst_dir, rel_entry) + continue + next_dirnames.append(dirname) + dirnames[:] = next_dirnames + + for filename in filenames: + src_file = os.path.join(current_root, filename) + if os.path.islink(src_file): + dst_file = os.path.join(target_root, filename) + rel_entry = os.path.join(relpath, os.path.relpath(src_file, src)) + merge_symlink(src_file, dst_file, rel_entry) + continue + dst_file = os.path.join(target_root, filename) + rel_file = os.path.join(relpath, os.path.relpath(src_file, src)) + merge_file(src_file, dst_file, rel_file) + + +def is_tracked_path(relpath: str, tracked: Set[str]) -> bool: + prefix = relpath + os.sep + return any(path == relpath or path.startswith(prefix) for path in tracked) + + +def discover_child_assets( + worktrees: Iterable[str], + parent_relpath: str, + skip_names: Iterable[str], + tracked: Set[str], +) -> Dict[str, AssetSpec]: + specs: Dict[str, AssetSpec] = {} + skip = set(skip_names) + for worktree in worktrees: + parent = os.path.join(worktree, parent_relpath) + if not os.path.isdir(parent): + continue + for dirpath, dirnames, filenames in os.walk(parent): + dirnames.sort() + filenames.sort() + rel_dir = os.path.relpath(dirpath, parent) + if rel_dir == ".": + children = list(dirnames) + list(filenames) + for name in children: + if name in skip: + continue + child = os.path.join(dirpath, name) + relpath = os.path.join(parent_relpath, name) + if is_tracked_path(relpath, tracked): + continue + kind = "dir" if os.path.isdir(child) else "file" + specs[relpath] = AssetSpec(relpath, kind) + dirnames[:] = [] + else: + dirnames[:] = [] + return specs + + +def discover_assets(worktrees: Iterable[str], shared_root: str) -> List[AssetSpec]: + tracked: Set[str] = set() + for worktree in worktrees: + tracked.update(tracked_paths(worktree)) + + specs: Dict[str, AssetSpec] = {} + for spec in FIXED_ASSETS: + if not is_tracked_path(spec.relpath, tracked): + specs[spec.relpath] = spec + for source_root in list(worktrees) + [shared_root]: + if os.path.isdir(os.path.join(source_root, "symbols")): + specs.update( + discover_child_assets( + [source_root], "symbols", skip_names=(), tracked=tracked + ).items() + ) + if os.path.isdir(os.path.join(source_root, "orig")): + for worktree in [source_root]: + orig_root = os.path.join(worktree, "orig") + if not os.path.isdir(orig_root): + continue + for version in sorted(os.listdir(orig_root)): + version_path = os.path.join(orig_root, version) + if not os.path.isdir(version_path): + continue + child_specs = discover_child_assets( + [worktree], + os.path.join("orig", version), + skip_names=(".gitkeep",), + tracked=tracked, + ) + specs.update(child_specs.items()) + return [specs[key] for key in sorted(specs)] + + +def asset_status(path: str, shared_path: str) -> str: + if same_symlink(path, shared_path): + return "shared" + if os.path.islink(path): + return "other-symlink" + if os.path.isdir(path): + return "local-dir" + if os.path.isfile(path): + return "local-file" + return "missing" + + +def ensure_shared_asset(spec: AssetSpec, worktrees: Iterable[str], shared_root: str) -> Optional[str]: + shared_path = os.path.join(shared_root, spec.relpath) + if spec.kind == "dir": + os.makedirs(shared_path, exist_ok=True) + found = False + for worktree in worktrees: + local_path = os.path.join(worktree, spec.relpath) + if same_symlink(local_path, shared_path) or not os.path.isdir(local_path): + continue + merge_tree(local_path, shared_path, spec.relpath) + found = True + if found or os.listdir(shared_path): + return shared_path + return None + + found = False + for worktree in worktrees: + local_path = os.path.join(worktree, spec.relpath) + if same_symlink(local_path, shared_path) or not os.path.isfile(local_path): + continue + merge_file(local_path, shared_path, spec.relpath) + found = True + if found or os.path.isfile(shared_path): + return shared_path + return None + + +def link_asset(worktree: str, spec: AssetSpec, shared_path: str) -> str: + local_path = os.path.join(worktree, spec.relpath) + if same_symlink(local_path, shared_path): + return "already-shared" + + if spec.kind == "dir": + if os.path.isdir(local_path): + merge_tree(local_path, shared_path, spec.relpath) + remove_path(local_path) + elif os.path.isfile(local_path): + raise RuntimeError(f"{spec.relpath}: expected directory in {worktree}") + elif os.path.islink(local_path): + remove_path(local_path) + else: + if os.path.isfile(local_path): + merge_file(local_path, shared_path, spec.relpath) + remove_path(local_path) + elif os.path.isdir(local_path): + raise RuntimeError(f"{spec.relpath}: expected file in {worktree}") + elif os.path.islink(local_path): + remove_path(local_path) + + ensure_parent(local_path) + os.symlink(shared_path, local_path) + return "linked" + + +def print_status(worktrees: List[str], shared_root: str) -> int: + assets = discover_assets(worktrees, shared_root) + print(f"Shared asset root: {shared_root}") + for worktree in worktrees: + print(f"\n[{worktree}]") + for spec in assets: + shared_path = os.path.join(shared_root, spec.relpath) + shared_state = "seeded" if lexists(shared_path) else "unseeded" + local_state = asset_status(os.path.join(worktree, spec.relpath), shared_path) + print(f" {spec.relpath:<40} {local_state:<12} {shared_state}") + return 0 + + +def link_assets(worktrees: List[str], shared_root: str) -> int: + os.makedirs(shared_root, exist_ok=True) + assets = discover_assets(worktrees, shared_root) + for spec in assets: + shared_path = ensure_shared_asset(spec, worktrees, shared_root) + if shared_path is None: + continue + for worktree in worktrees: + status = link_asset(worktree, spec, shared_path) + print(f"{worktree}: {spec.relpath} -> {status}") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser( + description=( + "Share stable debug/tool assets across git worktrees while keeping " + "generated build outputs local to each worktree." + ) + ) + parser.add_argument( + "command", + choices=("status", "link"), + help="Inspect or create shared asset symlinks.", + ) + parser.add_argument( + "--all", + action="store_true", + help="Operate on all worktrees for this repository (default: current worktree only).", + ) + args = parser.parse_args() + + common_dir = git_common_dir(root_dir) + shared_root = os.path.join(common_dir, SHARED_ROOT_NAME) + worktrees = list_worktrees(root_dir) if args.all else [root_dir] + + try: + if args.command == "status": + return print_status(worktrees, shared_root) + return link_assets(worktrees, shared_root) + except RuntimeError as e: + print(f"Error: {e}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) From ea02c5c4edb1bfe1c2def5655ebc13b59dea7ff1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 11 Mar 2026 20:08:09 +0100 Subject: [PATCH 003/372] AGENTS: forbid touching comparison inputs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index cdf83bc28..b278fa09f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,6 +42,22 @@ Sub-agents must **not** write or edit code files, headers, configs, or other rep All persistent file changes, decomp implementations, scaffolding, and follow-up fixes must be done by the main worker after reviewing the read-only findings. +## Forbidden Changes + +Do **not** edit or otherwise touch the comparison and configuration inputs that define the +project's match metrics: + +- `config/GOWE69/symbols.txt` +- `config/GOWE69/splits.txt` +- `configure.py` + +Treat these files as read-only unless the user explicitly asks for a task that is specifically +about maintaining that infrastructure. + +Do **not** try to cheat objdiff, progress, or match metrics in any way. The goal is to improve +the real decompilation output, not to manipulate the comparison setup, hide mismatches, or make +progress numbers look better without actually matching the original code. + ### lookup.py — Symbol lookup from the debug dump Query structs, enums, functions, globals, and typedefs directly from the pre-extracted From 8c384ccfadecb180350214cc125841cfd6363e64 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 12 Mar 2026 21:06:33 +0100 Subject: [PATCH 004/372] Improve decomp workflow tooling and agent guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/execute/SKILL.md | 35 +++- .github/skills/implement/SKILL.md | 26 ++- .github/skills/refiner/SKILL.md | 31 ++-- AGENTS.md | 32 +++- README.md | 22 ++- tools/_common.py | 166 ++++++++++++++++- tools/decomp-context.py | 173 +++++++++++++++++- tools/decomp-diff.py | 153 ++++++---------- tools/decomp-status.py | 101 +++++++++-- tools/decomp-workflow.py | 291 +++++++++++++++++++++++++++++- tools/project.py | 9 +- 11 files changed, 875 insertions(+), 164 deletions(-) diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index c6ebe9e22..c4366f2a6 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -55,14 +55,25 @@ Determine the file path (e.g. `src/Speed/Indep/SourceLists/zWorld2`). The game u Preferred shortcut: ```sh +python tools/decomp-workflow.py next --unit main/Path/To/TU --limit 10 python tools/decomp-workflow.py unit -u main/Path/To/TU --limit 20 ``` -If you need the raw tools instead of the wrapper, run `decomp-status.py` and -`decomp-diff.py` directly against the shared build output. +Use `next` first when you want the wrapper to rank the most useful targets instead of +following raw objdiff order. `--strategy balanced` is the default and is usually the best +starting point because it now favors large remaining gains and penalizes near-finished +cleanup work. Use `--strategy impact` when you only care about the biggest unmatched-byte +wins. Use `--strategy quick-wins` when you want lower-match functions where the first big +chunk of progress is likely to come faster than late cleanup. + +Stay in the wrapper flow by default. Only drop to raw `decomp-status.py` / `decomp-diff.py` +when you need an option the wrapper does not expose yet. -This shows all symbols with their match status. Note the total count of missing, -nonmatching, and matching functions. +If the shared unit object is missing, the wrapper now rebuilds it automatically before +running `next --unit` / `unit`. + +If you need the raw tools instead of the wrapper, run `decomp-status.py` and +`decomp-diff.py` directly against the shared build output as a fallback, not the default. ## Phase 2: Scaffold (if needed) @@ -84,7 +95,7 @@ python tools/decomp-workflow.py unit -u main/Path/To/TU ``` If you need the raw tools, rebuild normally and then run `decomp-diff.py` -directly on the unit. +directly on the unit only as a fallback. ### 3c. Implement each function sequentially @@ -129,7 +140,8 @@ view for one function. After every few functions, re-run the full status check: ```sh -python tools/decomp-diff.py -u main/Path/To/TU +python tools/decomp-workflow.py unit -u main/Path/To/TU +python tools/decomp-workflow.py next --unit main/Path/To/TU --limit 10 ``` Review progress and decide whether to: @@ -143,16 +155,19 @@ Review progress and decide whether to: When all functions have been attempted: ```sh -# Full status -python tools/decomp-diff.py -u main/Path/To/TU +# Wrapper-first unit summary +python tools/decomp-workflow.py unit -u main/Path/To/TU -# Check for any remaining mismatches -python tools/decomp-diff.py -u main/Path/To/TU -s nonmatching +# Focused remaining mismatches +python tools/decomp-workflow.py diff -u main/Path/To/TU -s nonmatching -t function # Verify no regressions ninja changes ``` +If you need a raw full-symbol dump beyond that, use `decomp-diff.py` only as a final +fallback. + For any remaining nonmatching functions, make one final pass using the implementation or refiner workflow with all context accumulated during the session. diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 52c063e0b..a81bb47c1 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -11,6 +11,19 @@ Your goal is to decompile a specific function: writing C++ source that compiles Collect data from **all** of these sources in parallel where possible. +If the function was not already chosen for you, pick it with the ranking wrapper first: + +```sh +python tools/decomp-workflow.py next --unit main/Path/To/TU --limit 10 +python tools/decomp-workflow.py next --category game --limit 10 +``` + +Prefer low-match, high-remaining targets here. Do not default to near-finished cleanup +functions unless the user explicitly wants a cleanup/refiner pass. + +Use the wrapper flow first throughout this skill. Drop to raw `decomp-context.py` or +`decomp-diff.py` only when the wrapper is missing a specific flag or you are debugging. + ### 1a. decomp-context.py Preferred shortcut: @@ -21,6 +34,9 @@ python tools/decomp-workflow.py function -u main/Path/To/TU -f FunctionName --br python tools/decomp-workflow.py diff -u main/Path/To/TU -d FunctionName ``` +If the shared unit object is missing, the wrapper now rebuilds it automatically before +running `next --unit` / `function` / `diff`. + If you only need one Ghidra view, add `--ghidra-version gc` or `--ghidra-version ps2` to keep the context run faster and shorter. @@ -30,7 +46,7 @@ need the full DWARF body with locals and nested inline info. Add `--brief` when you want a shorter helper view; it trims suggested commands and related-source hints while keeping the core source/status/diff context. -Equivalent manual form: +Equivalent manual fallback: ```sh python tools/decomp-context.py -u main/Path/To/TU -f FunctionName @@ -94,7 +110,7 @@ and assembly: Utilize the dwarf information that you get from the lookup skill heavily. -Don't add any comments. +Don't add explanatory comments during implementation unless you need to document a remaining DWARF mismatch. Don't use any temporary local variables that don't exist in the dwarf. @@ -128,10 +144,10 @@ If the build fails, fix compilation errors first. ```sh # Quick status -python tools/decomp-diff.py -u main/Path/To/TU --search FunctionName +python tools/decomp-workflow.py diff -u main/Path/To/TU --search FunctionName --limit 20 # Full instruction diff -python tools/decomp-diff.py -u main/Path/To/TU -d FunctionName +python tools/decomp-workflow.py diff -u main/Path/To/TU -d FunctionName ``` ### Interpreting the diff @@ -168,7 +184,7 @@ Repeat the build-diff cycle until the diff shows 100% match with no `~` lines: ```sh python tools/decomp-workflow.py build -u main/Path/To/TU -python tools/decomp-diff.py -u main/Path/To/TU -d FunctionName +python tools/decomp-workflow.py diff -u main/Path/To/TU -d FunctionName ``` Every mismatched instruction is a signal — don't settle for "close enough". diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index 761a92e37..82b2a2608 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -18,7 +18,19 @@ approaches that were tried before — instead, apply systematic lateral analysis ## Phase 1: Read the full diff without collapsing -First rebuild the unit normally, then diff: +Preferred shortcut: + +```sh +python tools/decomp-workflow.py diff -u main/Path/To/TU -d FunctionName --no-collapse +``` + +If the shared unit object is missing, the wrapper now rebuilds it automatically before +running `diff`. + +Stay in the wrapper flow for refiner passes unless you hit a wrapper limitation and need a +backend-only option. + +If you need the raw backend form instead of the wrapper, rebuild the unit and then run: ```sh python tools/decomp-workflow.py build -u main/Path/To/TU @@ -37,8 +49,7 @@ Read every instruction pair. Categorize each mismatch: | **Relocation offset** | `@stringBase0` or data offset differs | More string literals will shift this; add them in order | | **Virtual vs direct call** | `bl` vs indirect through vtable | Check const-qualifier; use `GetFoo()` vs `Foo()` | | **Inline vs outlined** | Extra call to helper vs inlined sequence | Force inline by rewriting the expression without calling the helper | -| **Missing `this->` dereference** | Wrong address in load/store | Ensure member access goes through the correct `this` pointer | -| **Loop structure** | `do/while` vs `for` vs `while` | Try all three forms; compiler emits different branch sequences | +| **Loop structure** | Guarded `do/while` from Ghidra or mismatched loop branches | Rewrite to the natural source form suggested by the control flow; in particular, a guarded `do/while` often needs to become a plain `for` loop | ## Phase 2: Systematic permutation strategies @@ -90,22 +101,18 @@ python tools/lookup.py ./symbols/Dwarf struct bMath Replace hand-rolled sequences with the correct inline call. -### 2e. Initializer list order +### 2e. Constructor initialization placement -Constructors compiled with GCC are sensitive to initializer list order. The DWARF -shows the canonical member order. If yours differs, reorder. +Only do this for constructors. Compare which members are initialized in the +initializer list versus the function body, and in what order. Initializer-list use +often stabilizes store order, but forcing every member into the initializer list can +also make the match worse. ### 2f. Cast type `static_cast` vs `static_cast` produces different assembly sequences on PPC (see `xoris` pattern in AGENTS.md). Check all casts. -### 2g. Compiler flag hint - -If none of the above resolve the mismatch, note the function address and consider -running `flag_permuter.py`. This is a last resort — only do this for a single -isolated function, not as a general strategy. - ## Phase 3: DWARF verification After any instruction match, verify the DWARF also matches. diff --git a/AGENTS.md b/AGENTS.md index 74c446bc7..b95d62190 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,6 +137,8 @@ Prefer this wrapper for routine agent-driven flows instead of manually chaining python tools/decomp-workflow.py health python tools/decomp-workflow.py health --smoke-build main/Speed/Indep/SourceLists/zAnim python tools/decomp-workflow.py health --smoke-dtk main/Speed/Indep/SourceLists/zAnim +python tools/decomp-workflow.py next --category game --limit 10 +python tools/decomp-workflow.py next --unit main/Speed/Indep/SourceLists/zAnim --limit 5 python tools/decomp-workflow.py build -u main/Speed/Indep/SourceLists/zAnim python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zAnim -d FindIOWin python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zAnim -f FindIOWin @@ -148,6 +150,31 @@ python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zAnim --sea The wrapper keeps the existing tools as the source of truth. It is intended to reduce repeated command chaining and to standardize routine worktree preflight checks for agents. +`next --unit`, `function`, `unit`, and `diff` now also auto-build the unit's shared `.o` +once when that output is missing, so wrapper-first inspection works more often on +half-prepared worktrees. + +In normal agent work, use the wrapper commands first. Drop to the raw backend tools only +when you specifically need a backend-only flag, are debugging a wrapper/backend discrepancy, +or are doing a final exhaustive check that the wrapper does not expose directly. + +When you do not already have a specific target in mind, start with `next` or `unit` +instead of picking functions in raw objdiff order. `next` is the fastest way to answer +"what should I work on now?": + +- `--strategy balanced` favors functions with large remaining gains, penalizes + high-match cleanup work, de-prioritizes obvious init/setup sinkholes, and prefers + targets with usable source context. +- `--strategy impact` is the blunt "largest unmatched byte loss first" view. +- `--strategy quick-wins` biases toward low-match functions where getting the first + 40-60% tends to be much faster than squeezing a polished function from 95% to 100%. + It should not be treated as a cleanup/polish mode. + +When choosing what to work on next, bias toward low-match, high-remaining functions. +As a rule of thumb, getting a function from 0% to 80% is usually much faster and higher +leverage than pushing a function from 90% to 100%. +Leave 85%+ cleanup and refiner-style polish for deliberate cleanup passes unless the +user explicitly wants that work or the function is directly blocking something else. `function` is the preferred context-gathering entrypoint: it bundles source excerpt, objdiff status/diff, compact GC DWARF function lookup, and Ghidra output in one run. @@ -167,8 +194,9 @@ repeated manual steps for future agents. On a newly updated or unusual worktree, run `python tools/decomp-workflow.py health` first. If it reports missing generated files such as `objdiff.json` or `build.ninja`, run `python configure.py` in that worktree before using the decomp wrappers. `health` also -checks the debug-symbol side of the setup now: GC/PS2 `symbols.txt`, GC DWARF lookup, -PS2 type lookup, and the GC debug line mapping. +checks the debug-symbol side of the setup now, plus the wrapper binaries themselves: +`objdiff-cli`, `dtk`, GC/PS2 `symbols.txt`, GC DWARF lookup, PS2 type lookup, and the +GC debug line mapping. ### find-symbol.py — Check for existing definitions before declaring new types diff --git a/README.md b/README.md index b74f298fd..70bb79dde 100644 --- a/README.md +++ b/README.md @@ -87,15 +87,15 @@ sudo xattr -rd com.apple.quarantine '/Applications/Wine Crossover.app' ``` - Extracting the binaries - - GC: Extract `NFSMWRELEASE.ELF`, copy it into `orig/GOWE69` and convert it into a DOL using the following command: + - GC: Extract `NFSMWRELEASE.ELF`, copy it into `orig/GOWE69`, and convert it into a DOL using the following command: ```sh - ./build/tools/dtk elf2dol ./orig/GOWE69/NFSMWRELEASE.elf ./orig/GOWE69/sys/main.dol + ./build/tools/dtk elf2dol ./orig/GOWE69/NFSMWRELEASE.ELF ./orig/GOWE69/sys/main.dol ``` - Xbox 360: simply rename `NfsMWEuropeGerMilestone.exe` to `NfsMWEuropeGerMilestone.xex` and copy it to `./orig/EUROPEGERMILESTONE/` - - PS2: Copy `NSF.ELF` to `./orig/SLES-53558-A124/` + - PS2: Copy `NFS.ELF` to `./orig/SLES-53558-A124/` - Sharing large assets across git worktrees @@ -108,8 +108,8 @@ sudo xattr -rd com.apple.quarantine '/Applications/Wine Crossover.app' ``` This shares the ignored debug/tool assets under the git common directory, including - extracted `orig/*` contents, `symbols/*`, root ELF / MAP files, and downloaded - tool binaries under `build/`. It intentionally does **not** share `build.ninja`, + extracted `orig/*` contents, `symbols/*`, and downloaded tool binaries under + `build/`. It intentionally does **not** share `build.ninja`, `objdiff.json`, `compile_commands.json`, or per-worktree object outputs. After linking shared assets into a worktree, regenerate that worktree's local build @@ -160,7 +160,7 @@ For PS2 binaries the deprecated version gives nicer results. ## symbols/mw_dwarfdump.nothpp ``` -./build/tools/dtk dwarf dump ./orig/NFSMWRELEASE.ELF -o ./symbols/mw_dwarfdump.nothpp +./build/tools/dtk dwarf dump ./orig/GOWE69/NFSMWRELEASE.ELF -o ./symbols/mw_dwarfdump.nothpp ``` This is the dwarf dump of the whole GC version of the game. The `.nothpp` extension is to make sure that the IDE doesn't parse it on weak laptops. This should be your main source of information. It even shows which inlines a function calls. Namespaces only show up in generics. For regular functions and variables you can search `symbols.txt` for the right name. @@ -204,13 +204,19 @@ This file contains bChunk chunk IDs. - Run ``` - ./build/tools/dtk dwarf dump ./orig/NFSMWRELEASE.ELF -o ./symbols/mw_dwarfdump.nothpp + ./build/tools/dtk dwarf dump ./orig/GOWE69/NFSMWRELEASE.ELF -o ./symbols/mw_dwarfdump.nothpp python ./tools/split_dwarf_info.py ./symbols/mw_dwarfdump.nothpp ./symbols/Dwarf ``` - Set up the project and Ghidra as described above (take the Ghidra repo from the decomp.dev server, you'll have to request access). -- In Ghidra, checkout `mw/GOWE69/NFSMWRELEASE.ELF` and `mw/SLES-53558/NFS.ELF.fixed` and copy them both into the root of the project. Rename `NFS.ELF.fixed` to `NFS.ELF`. +- Import the ELF files from `orig/` into the Ghidra project so the program names stay + `NFSMWRELEASE.ELF` and `NFS.ELF`: + + ```sh + ghidra import ./orig/GOWE69/NFSMWRELEASE.ELF + ghidra import ./orig/SLES-53558-A124/NFS.ELF + ``` - Download [ghidra-cli](https://github.com/akiselev/ghidra-cli) and put it into your path. diff --git a/tools/_common.py b/tools/_common.py index 976fcd649..db992bd8b 100644 --- a/tools/_common.py +++ b/tools/_common.py @@ -7,13 +7,19 @@ import subprocess import sys import tempfile -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, List, Optional, Sequence SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) BUILD_NINJA = os.path.join(ROOT_DIR, "build.ninja") OBJDIFF_JSON = os.path.join(ROOT_DIR, "objdiff.json") +OBJDIFF_DEFAULT_CONFIG_ARGS = [ + "-c", + "functionRelocDiffs=none", + "-c", + "ppc.calculatePoolRelocations=false", +] class ToolError(RuntimeError): @@ -112,6 +118,144 @@ def apply_base_obj_override( return found +def classify_objdiff_symbol(sym: Dict[str, Any]) -> str: + """Classify an objdiff symbol as 'function', 'object', or 'section'.""" + kind = sym.get("kind", "") + if kind == "SYMBOL_FUNCTION": + return "function" + if kind == "SYMBOL_OBJECT": + return "object" + if kind == "SYMBOL_SECTION": + return "section" + if "instructions" in sym: + return "function" + if "data_diff" in sym: + return "object" + return "unknown" + + +def objdiff_symbol_section(sym: Dict[str, Any], sections: List[Dict[str, Any]]) -> str: + """Determine which section a symbol belongs to.""" + name = sym.get("name", "") + if name.startswith("[."): + return name[1:].split("-")[0].rstrip("]") + if classify_objdiff_symbol(sym) == "function": + return ".text" + for sec in sections: + kind = sec.get("kind", "") + if kind in ("SECTION_DATA", "SECTION_BSS"): + return sec["name"] + return ".data" + + +def estimate_unmatched_bytes( + size: int, match_percent: Optional[float], status: str +) -> int: + """Estimate remaining unmatched bytes for a symbol.""" + size = max(int(size), 0) + if size == 0: + return 0 + if status in ("missing", "extra", "no_target", "no_source"): + return size + if status in ("match", "matching", "complete"): + return 0 + if match_percent is None: + return size + + clamped = max(0.0, min(float(match_percent), 100.0)) + if clamped >= 100.0: + return 0 + + unmatched = int(round(size * (100.0 - clamped) / 100.0)) + unmatched = max(1, unmatched) + return min(size, unmatched) + + +def build_objdiff_symbol_rows(diff_data: Dict[str, Any]) -> List[Dict[str, Any]]: + """Build normalized overview rows from objdiff JSON for both left and right symbols.""" + left_syms = diff_data.get("left", {}).get("symbols", []) + right_syms = diff_data.get("right", {}).get("symbols", []) + left_sections = diff_data.get("left", {}).get("sections", []) + right_sections = diff_data.get("right", {}).get("sections", []) + + rows: List[Dict[str, Any]] = [] + + for sym in left_syms: + sym_type = classify_objdiff_symbol(sym) + if sym_type in ("section", "unknown"): + continue + + size = int(sym.get("size", "0")) + if size == 0: + continue + + name = sym.get("demangled_name", sym.get("name", "?")) + section = objdiff_symbol_section(sym, left_sections) + target_symbol = sym.get("target_symbol") + match_percent = sym.get("match_percent") + + if target_symbol is None: + status = "missing" + elif match_percent is not None and match_percent >= 100.0: + status = "match" + elif match_percent is not None: + status = "nonmatching" + else: + status = "missing" + + rows.append( + { + "status": status, + "match_percent": match_percent, + "size": size, + "unmatched_bytes_est": estimate_unmatched_bytes( + size, match_percent, status + ), + "section": section, + "type": sym_type, + "name": name, + "symbol_name": sym.get("name", "?"), + "side": "left", + "left_symbol": sym, + "right_symbol": right_syms[target_symbol] + if target_symbol is not None and target_symbol < len(right_syms) + else None, + } + ) + + for sym in right_syms: + if sym.get("target_symbol") is not None: + continue + + sym_type = classify_objdiff_symbol(sym) + if sym_type in ("section", "unknown"): + continue + + size = int(sym.get("size", "0")) + if size == 0: + continue + + name = sym.get("demangled_name", sym.get("name", "?")) + section = objdiff_symbol_section(sym, right_sections) + rows.append( + { + "status": "extra", + "match_percent": None, + "size": size, + "unmatched_bytes_est": estimate_unmatched_bytes(size, None, "extra"), + "section": section, + "type": sym_type, + "name": name, + "symbol_name": sym.get("name", "?"), + "side": "right", + "left_symbol": None, + "right_symbol": sym, + } + ) + + return rows + + def run_objdiff_json( objdiff_cli: str, unit_name: str, @@ -123,6 +267,7 @@ def run_objdiff_json( ensure_project_prereqs() cmd = [objdiff_cli, "diff"] + cmd.extend(OBJDIFF_DEFAULT_CONFIG_ARGS) if extra_args: cmd.extend(extra_args) cmd.extend(["-u", unit_name, "-o", "-", "--format", "json"]) @@ -141,12 +286,19 @@ def run_objdiff_json( cwd = tmpdir try: - result = subprocess.run( - cmd, - cwd=cwd, - text=True, - capture_output=True, - ) + try: + result = subprocess.run( + cmd, + cwd=cwd, + text=True, + capture_output=True, + ) + except FileNotFoundError: + raise ToolError( + f"Missing objdiff-cli: {objdiff_cli}\n" + "Hint: ensure build/tools is populated in this worktree " + "(for example via the shared worktree assets setup)." + ) if result.returncode != 0: stderr = result.stderr hint_lines = [] diff --git a/tools/decomp-context.py b/tools/decomp-context.py index 637c8f35f..32a35fcae 100644 --- a/tools/decomp-context.py +++ b/tools/decomp-context.py @@ -24,7 +24,14 @@ import subprocess import sys from typing import Any, Dict, List, Optional, Tuple -from _common import ROOT_DIR, ToolError, fail, load_objdiff_config, run_objdiff_json +from _common import ( + ROOT_DIR, + ToolError, + build_objdiff_symbol_rows, + fail, + load_objdiff_config, + run_objdiff_json, +) script_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = ROOT_DIR @@ -43,6 +50,10 @@ RELATED_SOURCE_LIMIT = 8 BRIEF_RELATED_SOURCE_LIMIT = 3 BRIEF_SUGGESTED_COMMAND_LIMIT = 2 +LOW_UNMATCHED_HINT_THRESHOLD = 192 +HIGH_MATCH_HINT_THRESHOLD = 85.0 +LARGER_TARGET_RATIO = 3 +LARGER_TARGET_MIN_BYTES = 192 def load_project_config() -> Dict[str, Any]: @@ -338,6 +349,42 @@ def extract_source_for_function( return header + "".join(excerpt) +def source_excerpt_is_useful(source_path: str, excerpt: str) -> bool: + lines = [line.strip() for line in excerpt.splitlines()] + content_lines = [ + line + for line in lines + if line and not line.startswith("// Lines ") + ] + if not content_lines: + return False + + include_like = sum( + 1 + for line in content_lines + if line.startswith("#include") + or line.startswith("#pragma") + or line.startswith("#if") + or line.startswith("#endif") + or line.startswith("#define") + ) + + source_list_path = source_path.replace("\\", "/") + if "SourceLists/" in source_list_path: + if include_like == len(content_lines): + return False + if include_like >= max(2, len(content_lines) - 1): + return False + + useful_tokens = ("{", "}", "if ", "for ", "while ", "::", "return ", "=") + if include_like == len(content_lines) and not any( + token in excerpt for token in useful_tokens + ): + return False + + return True + + def extract_source_around_line( source_path: str, line_number: int, context_lines: int = SOURCE_CONTEXT_LINES ) -> Optional[str]: @@ -904,6 +951,114 @@ def format_suggested_commands( return "\n".join(lines) +def unit_progress_category(unit: Dict[str, Any]) -> Optional[str]: + categories = unit.get("metadata", {}).get("progress_categories", []) + if not categories: + return None + if len(categories) > 1: + return str(categories[1]) + return str(categories[0]) + + +def format_priority_guidance( + unit_name: str, + unit: Dict[str, Any], + diff_data: Optional[Dict[str, Any]], + current_symbol_name: Optional[str], + brief: bool = False, +) -> Optional[str]: + if diff_data is None or current_symbol_name is None: + return None + + function_rows = [ + row + for row in build_objdiff_symbol_rows(diff_data) + if row["side"] == "left" + and row["type"] == "function" + and row["status"] in ("missing", "nonmatching") + and row["unmatched_bytes_est"] > 0 + ] + if not function_rows: + return None + + function_rows.sort( + key=lambda row: (-row["unmatched_bytes_est"], -row["size"], row["name"].lower()) + ) + + current_row = None + for row in function_rows: + if row["symbol_name"] == current_symbol_name: + current_row = row + break + if current_row is None: + return None + + current_unmatched = int(current_row["unmatched_bytes_est"]) + current_match = current_row.get("match_percent") + if ( + current_unmatched > LOW_UNMATCHED_HINT_THRESHOLD + and (current_match is None or float(current_match) < HIGH_MATCH_HINT_THRESHOLD) + ): + return None + + unit_top = function_rows[0] + larger_unit_target = None + for row in function_rows: + if row["symbol_name"] == current_symbol_name: + continue + if ( + int(row["unmatched_bytes_est"]) >= LARGER_TARGET_MIN_BYTES + and int(row["unmatched_bytes_est"]) >= current_unmatched * LARGER_TARGET_RATIO + ): + larger_unit_target = row + break + + lines: List[str] = [] + if current_match is not None and float(current_match) >= HIGH_MATCH_HINT_THRESHOLD: + lines.append( + f"- Current function is already in cleanup/polish territory " + f"(~{current_unmatched}B remaining, {float(current_match):.1f}% matched)." + ) + else: + lines.append( + f"- Current function is already low-byte cleanup territory (~{current_unmatched}B remaining)." + ) + + if larger_unit_target is not None: + larger_match = larger_unit_target.get("match_percent") + larger_match_detail = "" + if larger_match is not None: + larger_match_detail = f", {float(larger_match):.1f}% matched" + lines.append( + f"- This unit still has a much larger target: " + f"{larger_unit_target['name']} " + f"(~{larger_unit_target['unmatched_bytes_est']}B remaining{larger_match_detail})." + ) + lines.append( + f"- Try: python tools/decomp-workflow.py function -u {unit_name} " + f"-f '{larger_unit_target['name']}'" + ) + else: + lines.append( + f"- This unit's largest remaining function is only ~{unit_top['unmatched_bytes_est']}B " + f"({unit_top['name']})." + ) + category = unit_progress_category(unit) + next_cmd = "python tools/decomp-workflow.py next --strategy balanced --limit 10" + if category: + next_cmd = ( + "python tools/decomp-workflow.py next " + f"--category {category} --strategy balanced --limit 10" + ) + lines.append(f"- For larger gains elsewhere, rerun: {next_cmd}") + + if brief: + if larger_unit_target is not None: + return "\n".join([lines[0], lines[2]]) + return "\n".join([lines[0], lines[2]]) + return "\n".join(lines) + + def main(): parser = argparse.ArgumentParser( description="Gather context for decomp function matching" @@ -992,7 +1147,11 @@ def main(): if not args.no_source: if source_path: excerpt = extract_source_for_function(source_path, right_sym) - if excerpt is not None and excerpt.strip(): + if ( + excerpt is not None + and excerpt.strip() + and source_excerpt_is_useful(source_path, excerpt) + ): label = "Source" if right_sym and right_sym.get("instructions"): # Check if we actually got line info @@ -1077,6 +1236,16 @@ def main(): ), ) + priority_guidance = format_priority_guidance( + args.unit, + unit, + diff_data, + mangled, + brief=args.brief, + ) + if priority_guidance: + print_section("Higher-impact targets right now", priority_guidance) + if not source_was_useful and args.no_source: print_section( "Related Source Files", diff --git a/tools/decomp-diff.py b/tools/decomp-diff.py index 0f3dba9e8..9f4653147 100644 --- a/tools/decomp-diff.py +++ b/tools/decomp-diff.py @@ -14,12 +14,16 @@ """ import argparse -import json import os -import subprocess import sys from typing import Any, Dict, List, Optional, Tuple -from _common import ROOT_DIR, ToolError, fail, run_objdiff_json +from _common import ( + ROOT_DIR, + ToolError, + build_objdiff_symbol_rows, + fail, + run_objdiff_json, +) root_dir = ROOT_DIR OBJDIFF_CLI = os.path.join(root_dir, "build", "tools", "objdiff-cli") @@ -30,45 +34,9 @@ def run_objdiff(unit: str, base_obj: Optional[str] = None) -> Dict[str, Any]: OBJDIFF_CLI, unit, base_obj=base_obj, - extra_args=["-c", "functionRelocDiffs=none"], root_dir=root_dir, ) - -def classify_symbol(sym: Dict[str, Any]) -> str: - """Classify a symbol as 'function', 'object', or 'section'.""" - kind = sym.get("kind", "") - if kind == "SYMBOL_FUNCTION": - return "function" - if kind == "SYMBOL_OBJECT": - return "object" - if kind == "SYMBOL_SECTION": - return "section" - # Fallback for external/relocation-only symbols (empty kind) - if "instructions" in sym: - return "function" - if "data_diff" in sym: - return "object" - return "unknown" - - -def symbol_section(sym: Dict[str, Any], sections: List[Dict[str, Any]]) -> str: - """Determine which section a symbol belongs to.""" - # For named section data symbols like [.rodata-0] - name = sym.get("name", "") - if name.startswith("[."): - return name[1:].split("-")[0].rstrip("]") - # Use content type as best indicator - if classify_symbol(sym) == "function": - return ".text" - # Check sections for data - for sec in sections: - kind = sec.get("kind", "") - if kind in ("SECTION_DATA", "SECTION_BSS"): - return sec["name"] - return ".data" - - def fuzzy_match(pattern: str, name: str) -> bool: """Case-insensitive substring match.""" return pattern.lower() in name.lower() @@ -94,72 +62,43 @@ def describe_pair_status( def build_overview(data: Dict[str, Any], args) -> None: """Print overview of all symbols in a unit.""" - left_syms = data.get("left", {}).get("symbols", []) - right_syms = data.get("right", {}).get("symbols", []) - left_sections = data.get("left", {}).get("sections", []) - right_sections = data.get("right", {}).get("sections", []) - - rows = [] - - # Process left (original/target) symbols - for i, sym in enumerate(left_syms): - sym_type = classify_symbol(sym) - # Skip section symbols and external references - if sym_type in ("section", "unknown"): - continue - # Skip symbols without size - size = int(sym.get("size", "0")) - if size == 0: - continue - - name = sym.get("demangled_name", sym.get("name", "?")) - section = symbol_section(sym, left_sections) - ts = sym.get("target_symbol") - mp = sym.get("match_percent") - - if ts is None: - status = "missing" - match_str = "-" - elif mp is not None and mp >= 100.0: - status = "match" - match_str = f"{mp:.1f}%" - elif mp is not None: - status = "nonmatching" - match_str = f"{mp:.1f}%" - else: - status = "missing" - match_str = "-" - - rows.append((status, match_str, size, section, sym_type, name, "left")) - - # Process right (decomp/base) symbols that aren't targeted (extra) - for i, sym in enumerate(right_syms): - if sym.get("target_symbol") is not None: - continue # Already covered via left side - sym_type = classify_symbol(sym) - if sym_type in ("section", "unknown"): - continue - size = int(sym.get("size", "0")) - if size == 0: - continue - name = sym.get("demangled_name", sym.get("name", "?")) - section = symbol_section(sym, right_sections) - rows.append(("extra", "-", size, section, sym_type, name, "right")) + rows = build_objdiff_symbol_rows(data) # Apply filters if args.type: types = set(t.strip() for t in args.type.split(",")) - rows = [r for r in rows if r[4] in types] + rows = [r for r in rows if r["type"] in types] if args.status: - statuses = set(s.strip() for s in args.status.split(",")) - rows = [r for r in rows if r[0] in statuses] + status_aliases = {"matching": "match", "matched": "match"} + statuses = set( + status_aliases.get(s.strip(), s.strip()) for s in args.status.split(",") + ) + rows = [r for r in rows if r["status"] in statuses] if args.section: - rows = [r for r in rows if r[3] == args.section] + rows = [r for r in rows if r["section"] == args.section] if args.search: - rows = [r for r in rows if fuzzy_match(args.search, r[5])] + rows = [r for r in rows if fuzzy_match(args.search, r["name"])] + + if args.sort == "unmatched": + rows.sort( + key=lambda r: (-r["unmatched_bytes_est"], -r["size"], r["name"].lower()) + ) + elif args.sort == "size": + rows.sort(key=lambda r: (-r["size"], r["name"].lower())) + elif args.sort == "match": + rows.sort( + key=lambda r: ( + r["match_percent"] is None, + r["match_percent"] if r["match_percent"] is not None else 101.0, + -r["size"], + r["name"].lower(), + ) + ) + elif args.sort == "name": + rows.sort(key=lambda r: r["name"].lower()) if args.limit is not None: rows = rows[: args.limit] @@ -169,10 +108,20 @@ def build_overview(data: Dict[str, Any], args) -> None: return # Print header - print(f"{'STATUS':<10} {'MATCH':>7} {'SIZE':>6} {'SECTION':<10} {'NAME'}") - print("-" * 80) - for status, match_str, size, section, sym_type, name, side in rows: - print(f"{status:<10} {match_str:>7} {size:>5}B {section:<10} {name}") + print( + f"{'STATUS':<10} {'MATCH':>7} {'UNMATCH':>8} {'SIZE':>6} {'SECTION':<10} {'NAME'}" + ) + print("-" * 96) + for row in rows: + match_str = ( + f"{row['match_percent']:.1f}%" + if row["match_percent"] is not None + else "-" + ) + print( + f"{row['status']:<10} {match_str:>7} {row['unmatched_bytes_est']:>7}B " + f"{row['size']:>5}B {row['section']:<10} {row['name']}" + ) def render_instruction( @@ -458,6 +407,12 @@ def main(): type=int, help="Limit overview output to the first N matching rows", ) + parser.add_argument( + "--sort", + choices=["objdiff", "unmatched", "size", "match", "name"], + default="objdiff", + help="Sort overview rows (default: objdiff order)", + ) # Diff options parser.add_argument( diff --git a/tools/decomp-status.py b/tools/decomp-status.py index 8741b9675..8dcf5d6c7 100644 --- a/tools/decomp-status.py +++ b/tools/decomp-status.py @@ -18,7 +18,14 @@ import os import sys from typing import Any, Dict, List, Optional, Tuple -from _common import ROOT_DIR, ToolError, fail, load_objdiff_config, run_objdiff_json +from _common import ( + ROOT_DIR, + ToolError, + build_objdiff_symbol_rows, + fail, + load_objdiff_config, + run_objdiff_json, +) root_dir = ROOT_DIR @@ -41,9 +48,17 @@ def run_objdiff(unit_name: str) -> Tuple[Optional[Dict[str, Any]], Optional[str] def analyze_unit(diff_data: Dict[str, Any]) -> Dict[str, Any]: """Analyze a unit's diff data and return summary statistics.""" left = diff_data.get("left", {}) - right = diff_data.get("right", {}) - left_syms = left.get("symbols", []) left_sections = left.get("sections", []) + symbol_rows = build_objdiff_symbol_rows(diff_data) + function_rows = [r for r in symbol_rows if r["type"] == "function" and r["side"] == "left"] + unmatched_function_rows = [ + r + for r in function_rows + if r["status"] in ("missing", "nonmatching") and r["unmatched_bytes_est"] > 0 + ] + unmatched_function_rows.sort( + key=lambda r: (-r["unmatched_bytes_est"], -r["size"], r["name"].lower()) + ) # Section-level stats section_stats = {} @@ -65,17 +80,17 @@ def analyze_unit(diff_data: Dict[str, Any]) -> Dict[str, Any]: matching_funcs = 0 total_code_size = 0 matching_code_size = 0 + remaining_code_size_est = 0 - for sym in left_syms: - if "instructions" not in sym: - continue - size = int(sym.get("size", "0")) + for row in function_rows: + size = row["size"] total_funcs += 1 total_code_size += size - mp = sym.get("match_percent") + mp = row["match_percent"] if mp is not None and mp >= 100.0: matching_funcs += 1 matching_code_size += size + remaining_code_size_est += row["unmatched_bytes_est"] text_section = section_stats.get(".text", {}) text_match = text_section.get("match_percent") @@ -86,9 +101,20 @@ def analyze_unit(diff_data: Dict[str, Any]) -> Dict[str, Any]: "matching_functions": matching_funcs, "total_code_size": total_code_size, "matching_code_size": matching_code_size, + "remaining_code_size_est": remaining_code_size_est, "text_match_percent": text_match, "text_size": text_size, "sections": section_stats, + "top_unmatched_functions": [ + { + "name": row["name"], + "status": row["status"], + "size": row["size"], + "match_percent": row["match_percent"], + "unmatched_bytes_est": row["unmatched_bytes_est"], + } + for row in unmatched_function_rows[:10] + ], } @@ -105,6 +131,12 @@ def main(): dest="json_output", help="Output as JSON", ) + parser.add_argument( + "--top-unmatched", + type=int, + metavar="N", + help="Show the top N unmatched functions by estimated unmatched bytes", + ) args = parser.parse_args() config = load_project_config() @@ -180,7 +212,9 @@ def main(): grand_matching_funcs = 0 grand_total_size = 0 grand_matching_size = 0 + grand_remaining_size_est = 0 cat_summaries = {} + top_unmatched_candidates = [] for cat, entries in sorted(results.items()): print(f"\n=== {cat} ===") @@ -206,13 +240,26 @@ def main(): mf = entry.get("matching_functions", 0) tm = entry.get("text_match_percent") tm_str = f"{tm:.1f}%" if tm is not None else "?" + remain = entry.get("remaining_code_size_est", 0) print( - f" {display_name:<50s} .text {tm_str:>6s} ({mf}/{tf} functions)" + f" {display_name:<50s} .text {tm_str:>6s} ~{remain:>6}B rem ({mf}/{tf} functions)" ) cat_funcs += tf cat_matching += mf cat_size += entry.get("total_code_size", 0) cat_matching_size += entry.get("matching_code_size", 0) + for candidate in entry.get("top_unmatched_functions", []): + top_unmatched_candidates.append( + { + "unit": name, + "display_unit": display_name, + "name": candidate["name"], + "status": candidate["status"], + "size": candidate["size"], + "match_percent": candidate["match_percent"], + "unmatched_bytes_est": candidate["unmatched_bytes_est"], + } + ) elif status == "no_source": if args.unit: print(f" {display_name:<50s} no source file") @@ -235,11 +282,17 @@ def main(): "matching": cat_matching, "complete_units": complete_count, "total_units": len(entries), + "remaining_code_size_est": sum( + e.get("remaining_code_size_est", 0) + for e in entries + if e.get("status") == "incomplete" + ), } grand_total_funcs += cat_funcs grand_matching_funcs += cat_matching grand_total_size += cat_size grand_matching_size += cat_matching_size + grand_remaining_size_est += cat_summaries[cat]["remaining_code_size_est"] if not args.unit: print(f"\n=== Summary ===") @@ -251,14 +304,40 @@ def main(): pct = f"{matching/total*100:.1f}%" if total > 0 else "N/A" print( f" {cat:<15s} {pct:>6s} ({matching}/{total} functions) " - f"[{complete}/{total_units} units complete]" + f"[{complete}/{total_units} units complete, ~{s['remaining_code_size_est']}B rem]" ) if grand_total_funcs > 0: grand_pct = grand_matching_funcs / grand_total_funcs * 100 print( - f"\n Total: {grand_pct:.1f}% ({grand_matching_funcs}/{grand_total_funcs} functions)" + f"\n Total: {grand_pct:.1f}% ({grand_matching_funcs}/{grand_total_funcs} functions, ~{grand_remaining_size_est}B rem)" ) + if args.top_unmatched: + top_unmatched_candidates.sort( + key=lambda r: (-r["unmatched_bytes_est"], -r["size"], r["name"].lower()) + ) + if args.top_unmatched > 0: + top_unmatched_candidates = top_unmatched_candidates[: args.top_unmatched] + + print("\n=== Top Unmatched Functions ===") + if not top_unmatched_candidates: + print("No unmatched functions found for the given filters.") + else: + print( + f"{'UNMATCH':>8} {'MATCH':>7} {'SIZE':>6} {'UNIT':<34} NAME" + ) + print("-" * 110) + for candidate in top_unmatched_candidates: + match_str = ( + f"{candidate['match_percent']:.1f}%" + if candidate["match_percent"] is not None + else "-" + ) + print( + f"{candidate['unmatched_bytes_est']:>7}B {match_str:>7} " + f"{candidate['size']:>5}B {candidate['display_unit']:<34} {candidate['name']}" + ) + if __name__ == "__main__": main() diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index c4dc08b39..bca622ec0 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -18,12 +18,14 @@ """ import argparse +import json import re import os +import shlex import subprocess import sys import tempfile -from typing import List, Optional, Sequence +from typing import Any, Dict, List, Optional, Sequence from _common import ( BUILD_NINJA, OBJDIFF_JSON, @@ -40,6 +42,7 @@ TOOLS_DIR = os.path.join(ROOT_DIR, "tools") PS2_TYPES = os.path.join(ROOT_DIR, "symbols", "PS2", "PS2_types.nothpp") DTK = os.path.join(ROOT_DIR, "build", "tools", "dtk") +OBJDIFF_CLI = os.path.join(ROOT_DIR, "build", "tools", "objdiff-cli") GC_SYMBOLS = os.path.join(ROOT_DIR, "config", "GOWE69", "symbols.txt") PS2_SYMBOLS = os.path.join(ROOT_DIR, "config", "SLES-53558-A124", "symbols.txt") GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") @@ -49,12 +52,15 @@ DEBUG_SYMBOL_PROBE_MANGLED = "UpdateAll__6Cameraf" DEBUG_SYMBOL_PROBE_DEMANGLED = "Camera::UpdateAll(float)" DEBUG_SYMBOL_PROBE_GC_ADDR = "0x80065A84" +LOW_MATCH_PRIORITY_THRESHOLD = 60.0 +VERY_LOW_MATCH_PRIORITY_THRESHOLD = 40.0 +HIGH_MATCH_CLEANUP_THRESHOLD = 85.0 +VERY_HIGH_MATCH_CLEANUP_THRESHOLD = 95.0 SHARED_ASSET_REQUIREMENTS = [ (os.path.join("build", "tools"), "downloaded tooling"), (os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "GameCube original ELF"), (os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "PS2 original ELF"), - (os.path.join("orig", "SLES-53558-A124", "NFS.MAP"), "PS2 MAP"), (os.path.join("symbols", "Dwarf"), "DWARF dump"), ] @@ -138,13 +144,42 @@ def get_unit_build_output(unit_name: str) -> str: return make_abs(target) or target -def build_shared_unit(unit_name: str) -> str: +def build_shared_unit(unit_name: str, quiet: bool = False) -> str: ensure_decomp_prereqs() target = get_unit_build_target(unit_name) - run_stream(["ninja", target]) + cmd = ["ninja", target] + if quiet: + result = subprocess.run( + cmd, + cwd=ROOT_DIR, + text=True, + capture_output=True, + ) + if result.returncode != 0: + raise WorkflowError( + format_failure(cmd, result.returncode, result.stdout, result.stderr) + ) + else: + run_stream(cmd) return get_unit_build_output(unit_name) +def ensure_shared_unit_output(unit_name: str) -> str: + output_path = get_unit_build_output(unit_name) + if os.path.exists(output_path): + return output_path + + print(f"Shared build missing for {unit_name}; rebuilding...", flush=True) + try: + output_path = build_shared_unit(unit_name, quiet=True) + except WorkflowError as e: + raise WorkflowError( + f"Auto-build failed while preparing shared output for {unit_name}\n{e}" + ) + print(f"Shared build ready: {output_path}", flush=True) + return output_path + + def maybe_remove(path: Optional[str]) -> None: if not path: return @@ -240,6 +275,16 @@ def report(ok: bool, label: str, detail: str) -> None: ) print_section("Tool Checks") + report( + os.path.exists(OBJDIFF_CLI), + "objdiff-cli", + OBJDIFF_CLI if os.path.exists(OBJDIFF_CLI) else "missing (seed build/tools in this worktree)", + ) + report( + os.path.exists(DTK), + "dtk", + DTK if os.path.exists(DTK) else "missing (seed build/tools in this worktree)", + ) try: run_capture(python_tool("decomp-context.py", "--ghidra-check")) report(True, "ghidra", "GC + PS2 programs available") @@ -315,9 +360,133 @@ def report(ok: bool, label: str, detail: str) -> None: raise WorkflowError(f"Health check failed with {failures} issue(s)") +def build_next_candidates( + status_data: Dict[str, Any], strategy: str +) -> List[Dict[str, Any]]: + candidates: List[Dict[str, Any]] = [] + + for category, entries in status_data.items(): + for entry in entries: + unit_name = entry.get("name", "") + display_unit = unit_name.replace("main/", "") + has_source = bool(entry.get("has_source")) + + for func in entry.get("top_unmatched_functions", []): + function_name = func.get("name", "?") + unmatched = int(func.get("unmatched_bytes_est", 0)) + match_percent = func.get("match_percent") + status = func.get("status", "?") + size = int(func.get("size", 0)) + is_static_init = function_name.startswith( + "__static_initialization_and_destruction_0" + ) + is_initializer = "InitializeTables" in function_name or is_static_init + reason = "largest remaining byte win" + score = float(unmatched) + + if strategy == "balanced": + if status == "missing": + score *= 1.15 + reason = "whole implementation still missing; high remaining gain" + elif status == "nonmatching": + score *= 1.05 + reason = "large remaining win" + + if match_percent is not None: + if match_percent >= VERY_HIGH_MATCH_CLEANUP_THRESHOLD: + score *= 0.2 + reason = ( + "near-finished cleanup deprioritized in favor of larger remaining gains" + ) + elif match_percent >= HIGH_MATCH_CLEANUP_THRESHOLD: + score *= 0.45 + reason = ( + "high-match cleanup deprioritized in favor of larger remaining gains" + ) + elif match_percent <= VERY_LOW_MATCH_PRIORITY_THRESHOLD: + score *= 1.25 + reason = "low match % leaves a large amount of work and upside" + elif match_percent <= LOW_MATCH_PRIORITY_THRESHOLD: + score *= 1.1 + reason = "plenty of unmatched work remains here" + + if has_source: + score *= 1.08 + if "source available" not in reason and "deprioritized" not in reason: + reason += " with source available" + if is_initializer: + score *= 0.3 + reason = ( + "large remaining win, but likely lower-priority init/setup work" + ) + elif strategy == "quick-wins": + score = min(float(unmatched), 1024.0) + if status == "missing": + score *= 1.05 + reason = "whole implementation missing; early progress should come quickly" + elif status == "nonmatching": + score *= 1.1 + reason = "partial implementation exists, but this is still early-progress work" + + if match_percent is None: + score *= 1.35 + reason = "0% function; early implementation progress is usually fastest" + elif match_percent <= VERY_LOW_MATCH_PRIORITY_THRESHOLD: + score *= 1.35 + reason = "very low match % leaves fast early-progress gains" + elif match_percent <= LOW_MATCH_PRIORITY_THRESHOLD: + score *= 1.2 + reason = "low match % usually moves faster than cleanup" + elif match_percent >= VERY_HIGH_MATCH_CLEANUP_THRESHOLD: + score *= 0.12 + reason = "near-finished cleanup is slower than fresh early-progress work" + elif match_percent >= HIGH_MATCH_CLEANUP_THRESHOLD: + score *= 0.35 + reason = "high-match cleanup deprioritized; quicker gains exist earlier" + elif match_percent >= 70.0: + score *= 0.75 + reason = "mid/high-match work is less likely to be a quick win" + if has_source: + score *= 1.05 + if "source" not in reason: + reason += " with source available" + if is_initializer: + score *= 0.1 + reason = ( + "deprioritized init/setup work; likely not the fastest useful win" + ) + + candidates.append( + { + "category": category, + "unit": unit_name, + "display_unit": display_unit, + "function": function_name, + "status": status, + "size": size, + "match_percent": match_percent, + "unmatched_bytes_est": unmatched, + "score": score, + "reason": reason, + } + ) + + candidates.sort( + key=lambda c: ( + -c["score"], + c["match_percent"] if c["match_percent"] is not None else -1.0, + -c["unmatched_bytes_est"], + -c["size"], + c["function"].lower(), + ) + ) + return candidates + + def command_function(args: argparse.Namespace) -> None: ensure_decomp_prereqs() print_section(f"Function Workflow: {args.function}") + ensure_shared_unit_output(args.unit) cmd = python_tool("decomp-context.py", "-u", args.unit, "-f", args.function) if args.no_source: cmd.append("--no-source") @@ -337,13 +506,24 @@ def command_function(args: argparse.Namespace) -> None: def command_unit(args: argparse.Namespace) -> None: ensure_decomp_prereqs() print_section(f"Unit Status: {args.unit}") - run_stream(python_tool("decomp-status.py", "--unit", args.unit)) + ensure_shared_unit_output(args.unit) + top_unmatched_limit = args.limit if args.limit is not None else 5 + run_stream( + python_tool( + "decomp-status.py", + "--unit", + args.unit, + "--top-unmatched", + str(top_unmatched_limit), + ) + ) common_args: List[str] = ["-u", args.unit, "-t", "function"] if args.search: common_args.extend(["--search", args.search]) if args.limit is not None: common_args.extend(["--limit", str(args.limit)]) + common_args.extend(["--sort", "unmatched"]) print_section("Missing Functions") run_stream(python_tool("decomp-diff.py", *common_args, "-s", "missing")) @@ -352,6 +532,78 @@ def command_unit(args: argparse.Namespace) -> None: run_stream(python_tool("decomp-diff.py", *common_args, "-s", "nonmatching")) +def command_next(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + if args.unit: + ensure_shared_unit_output(args.unit) + + cmd = python_tool("decomp-status.py", "--json") + if args.category: + cmd.extend(["--category", args.category]) + if args.unit: + cmd.extend(["--unit", args.unit]) + + result = run_capture(cmd) + status_data = json.loads(result.stdout) + candidates = build_next_candidates(status_data, args.strategy) + if args.limit is not None: + candidates = candidates[: args.limit] + + if not candidates: + if args.unit: + for entries in status_data.values(): + for entry in entries: + if entry.get("name") != args.unit: + continue + status = entry.get("status") + if status == "error": + raise WorkflowError( + f"Unable to rank {args.unit}: {entry.get('error_message', 'objdiff failed')}" + ) + if status == "complete": + raise WorkflowError(f"{args.unit} is already complete.") + if status == "no_source": + raise WorkflowError( + f"{args.unit} has no decomp source configured in objdiff.json." + ) + if status == "no_target": + raise WorkflowError( + f"{args.unit} has no target object configured in objdiff.json." + ) + raise WorkflowError("No unmatched function candidates found for the given filters.") + + if args.command_only: + for candidate in candidates: + print( + "python tools/decomp-workflow.py function " + f"-u {shlex.quote(candidate['unit'])} " + f"-f {shlex.quote(candidate['function'])}" + ) + return + + print_section("Next Targets") + print( + f"{'UNMATCH':>8} {'MATCH':>7} {'SIZE':>6} {'UNIT':<34} {'FUNCTION'}" + ) + print("-" * 120) + for candidate in candidates: + match_str = ( + f"{candidate['match_percent']:.1f}%" + if candidate["match_percent"] is not None + else "-" + ) + print( + f"{candidate['unmatched_bytes_est']:>7}B {match_str:>7} {candidate['size']:>5}B " + f"{candidate['display_unit']:<34} {candidate['function']}" + ) + print(f" why: {candidate['reason']}") + print( + " next: python tools/decomp-workflow.py function " + f"-u {shlex.quote(candidate['unit'])} " + f"-f {shlex.quote(candidate['function'])}" + ) + + def command_build(args: argparse.Namespace) -> None: print(build_shared_unit(args.unit), flush=True) @@ -362,6 +614,7 @@ def command_diff(args: argparse.Namespace) -> None: if args.diff: title += f" / {args.diff}" print_section(title) + ensure_shared_unit_output(args.unit) cmd: List[str] = python_tool("decomp-diff.py", "-u", args.unit) if args.diff: @@ -472,6 +725,34 @@ def build_parser() -> argparse.ArgumentParser: ) unit.set_defaults(func=command_unit) + next_cmd = subparsers.add_parser( + "next", + help="Recommend the highest-impact next functions to work on", + ) + next_cmd.add_argument("--category", help="Filter by progress category") + next_cmd.add_argument("--unit", help="Restrict recommendations to one unit") + next_cmd.add_argument( + "--limit", + type=int, + default=10, + help="Limit the number of suggested targets (default: 10)", + ) + next_cmd.add_argument( + "--strategy", + choices=["impact", "balanced", "quick-wins"], + default="balanced", + help=( + "Ranking strategy for recommendations (default: balanced; quick-wins favors " + "low-match functions where early progress is fastest)" + ), + ) + next_cmd.add_argument( + "--command-only", + action="store_true", + help="Print only follow-up commands, one per line", + ) + next_cmd.set_defaults(func=command_next) + build = subparsers.add_parser( "build", help="Build a unit's shared output with its configured ninja target", diff --git a/tools/project.py b/tools/project.py index ee35a3fa9..6fd752059 100644 --- a/tools/project.py +++ b/tools/project.py @@ -226,9 +226,12 @@ def __init__(self) -> None: self.print_progress_categories: Union[bool, List[str]] = ( True # Print additional progress categories in the CLI progress output ) - self.progress_report_args: Optional[List[str]] = ( - None # Flags to `objdiff-cli report generate` - ) + self.progress_report_args: Optional[List[str]] = [ + "-c", + "functionRelocDiffs=none", + "-c", + "ppc.calculatePoolRelocations=false", + ] # Flags to `objdiff-cli report generate` # Progress fancy printing self.progress_use_fancy: bool = False From 85b45a3d2b72f2d7c3150902f1130068ade493de Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 12 Mar 2026 21:37:20 +0100 Subject: [PATCH 005/372] Bootstrap fresh worktree setup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 30 ++------- tools/decomp-workflow.py | 13 +++- tools/share_worktree_assets.py | 114 ++++++++++++++++++++++++++++++--- 3 files changed, 121 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d89e285a1..b10d186c0 100644 --- a/README.md +++ b/README.md @@ -112,34 +112,16 @@ sudo xattr -rd com.apple.quarantine '/Applications/Wine Crossover.app' `build/`. It intentionally does **not** share `build.ninja`, `objdiff.json`, `compile_commands.json`, or per-worktree object outputs. - After linking shared assets into a worktree, regenerate that worktree's local build - files with: + After creating a fresh worktree, bootstrap its local generated files with: ```sh - python configure.py - ``` - -- Sharing large assets across git worktrees - - If you use multiple git worktrees, you can deduplicate the large immutable inputs - and downloaded tool binaries while keeping each worktree's generated build files - separate: - - ```sh - python tools/share_worktree_assets.py link --all + python tools/share_worktree_assets.py bootstrap ``` - This shares the ignored debug/tool assets under the git common directory, including - extracted `orig/*` contents, `symbols/*`, root ELF / MAP files, and downloaded - tool binaries under `build/`. It intentionally does **not** share `build.ninja`, - `objdiff.json`, `compile_commands.json`, or per-worktree object outputs. - - After linking shared assets into a worktree, regenerate that worktree's local build - files with: - - ```sh - python configure.py - ``` + `bootstrap` links the shared assets for the current worktree, runs `configure.py`, + generates the local split config when needed, and reruns `configure.py` so fresh + worktrees end up with `build.ninja`, `objdiff.json`, and `compile_commands.json` + without manual copying. # Diffing diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index bca622ec0..f9e9a0fc8 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -257,12 +257,16 @@ def report(ok: bool, label: str, detail: str) -> None: report( os.path.exists(BUILD_NINJA), "build.ninja", - BUILD_NINJA if os.path.exists(BUILD_NINJA) else "missing (run: python configure.py)", + BUILD_NINJA + if os.path.exists(BUILD_NINJA) + else "missing (run: python tools/share_worktree_assets.py bootstrap)", ) report( os.path.exists(OBJDIFF_JSON), "objdiff.json", - OBJDIFF_JSON if os.path.exists(OBJDIFF_JSON) else "missing (run: python configure.py)", + OBJDIFF_JSON + if os.path.exists(OBJDIFF_JSON) + else "missing (run: python tools/share_worktree_assets.py bootstrap)", ) print_section("Shared Assets") @@ -342,7 +346,10 @@ def report(ok: bool, label: str, detail: str) -> None: output_path = build_shared_unit(args.smoke_build) report(True, "build", output_path) except WorkflowError as e: - report(False, "build", str(e)) + detail = str(e) + if "objdiff.json" in detail or "build.ninja" in detail: + detail += "\nHint: Run: python tools/share_worktree_assets.py bootstrap" + report(False, "build", detail) if args.smoke_dtk: print_section("DTK Smoke Test") diff --git a/tools/share_worktree_assets.py b/tools/share_worktree_assets.py index f01f36145..374d168e5 100644 --- a/tools/share_worktree_assets.py +++ b/tools/share_worktree_assets.py @@ -12,6 +12,7 @@ python tools/share_worktree_assets.py status python tools/share_worktree_assets.py status --all python tools/share_worktree_assets.py link --all + python tools/share_worktree_assets.py bootstrap """ import argparse @@ -27,6 +28,7 @@ root_dir = os.path.abspath(os.path.join(script_dir, "..")) SHARED_ROOT_NAME = "worktree-shared" +DEFAULT_BOOTSTRAP_VERSION = "GOWE69" @dataclass(frozen=True) @@ -58,6 +60,24 @@ def run_git(args: List[str], cwd: str) -> str: return result.stdout +def run_command( + args: List[str], cwd: str, description: str, echo_success_output: bool = False +) -> subprocess.CompletedProcess[str]: + result = subprocess.run(args, cwd=cwd, capture_output=True, text=True) + if result.returncode != 0: + message = [f"{description} failed in {cwd}: {' '.join(args)}"] + if result.stdout.strip(): + message.append(f"stdout:\n{result.stdout.strip()}") + if result.stderr.strip(): + message.append(f"stderr:\n{result.stderr.strip()}") + raise RuntimeError("\n".join(message)) + if echo_success_output and result.stdout.strip(): + print(result.stdout.strip()) + if echo_success_output and result.stderr.strip(): + print(result.stderr.strip(), file=sys.stderr) + return result + + def git_common_dir(cwd: str) -> str: common = run_git(["rev-parse", "--git-common-dir"], cwd).strip() if os.path.isabs(common): @@ -302,19 +322,70 @@ def print_status(worktrees: List[str], shared_root: str) -> int: return 0 -def link_assets(worktrees: List[str], shared_root: str) -> int: +def link_assets( + target_worktrees: List[str], seed_worktrees: List[str], shared_root: str +) -> int: os.makedirs(shared_root, exist_ok=True) - assets = discover_assets(worktrees, shared_root) + assets = discover_assets(seed_worktrees, shared_root) for spec in assets: - shared_path = ensure_shared_asset(spec, worktrees, shared_root) + shared_path = ensure_shared_asset(spec, seed_worktrees, shared_root) if shared_path is None: continue - for worktree in worktrees: + for worktree in target_worktrees: status = link_asset(worktree, spec, shared_path) print(f"{worktree}: {spec.relpath} -> {status}") return 0 +def bootstrap_generated_files(worktree: str, version: str) -> None: + build_ninja = os.path.join(worktree, "build.ninja") + objdiff_json = os.path.join(worktree, "objdiff.json") + compile_commands = os.path.join(worktree, "compile_commands.json") + config_target = os.path.join("build", version, "config.json") + + print(f"{worktree}: running configure.py") + run_command([sys.executable, "configure.py"], worktree, "configure.py") + + if not os.path.isfile(build_ninja): + raise RuntimeError(f"{worktree}: configure.py did not create build.ninja") + + if not os.path.isfile(objdiff_json) or not os.path.isfile(compile_commands): + print(f"{worktree}: generating {config_target} for local objdiff metadata") + run_command(["ninja", config_target], worktree, f"ninja {config_target}") + print(f"{worktree}: rerunning configure.py") + run_command([sys.executable, "configure.py"], worktree, "configure.py") + + missing = [] + if not os.path.isfile(objdiff_json): + missing.append("objdiff.json") + if not os.path.isfile(compile_commands): + missing.append("compile_commands.json") + if missing: + raise RuntimeError( + f"{worktree}: bootstrap did not create {', '.join(missing)}" + ) + + +def bootstrap_worktrees( + target_worktrees: List[str], + seed_worktrees: List[str], + shared_root: str, + version: str, + run_health: bool, + smoke_build: Optional[str], +) -> int: + link_assets(target_worktrees, seed_worktrees, shared_root) + for worktree in target_worktrees: + bootstrap_generated_files(worktree, version) + if run_health or smoke_build: + cmd = [sys.executable, os.path.join("tools", "decomp-workflow.py"), "health"] + if smoke_build: + cmd.extend(["--smoke-build", smoke_build]) + print(f"{worktree}: running {' '.join(cmd)}") + run_command(cmd, worktree, "decomp-workflow health", echo_success_output=True) + return 0 + + def main() -> int: parser = argparse.ArgumentParser( description=( @@ -324,24 +395,49 @@ def main() -> int: ) parser.add_argument( "command", - choices=("status", "link"), - help="Inspect or create shared asset symlinks.", + choices=("status", "link", "bootstrap"), + help="Inspect, link, or fully bootstrap worktree-local setup.", ) parser.add_argument( "--all", action="store_true", help="Operate on all worktrees for this repository (default: current worktree only).", ) + parser.add_argument( + "--version", + default=DEFAULT_BOOTSTRAP_VERSION, + help="Version whose split config should be generated during bootstrap (default: GOWE69).", + ) + parser.add_argument( + "--health", + action="store_true", + help="Run `decomp-workflow.py health` after bootstrap completes.", + ) + parser.add_argument( + "--smoke-build", + metavar="UNIT", + help="Also run `decomp-workflow.py health --smoke-build UNIT` after bootstrap.", + ) args = parser.parse_args() common_dir = git_common_dir(root_dir) shared_root = os.path.join(common_dir, SHARED_ROOT_NAME) - worktrees = list_worktrees(root_dir) if args.all else [root_dir] + seed_worktrees = list_worktrees(root_dir) + target_worktrees = seed_worktrees if args.all else [root_dir] try: if args.command == "status": - return print_status(worktrees, shared_root) - return link_assets(worktrees, shared_root) + return print_status(target_worktrees, shared_root) + if args.command == "link": + return link_assets(target_worktrees, seed_worktrees, shared_root) + return bootstrap_worktrees( + target_worktrees, + seed_worktrees, + shared_root, + args.version, + args.health, + args.smoke_build, + ) except RuntimeError as e: print(f"Error: {e}", file=sys.stderr) return 1 From 4453407039a188cf62d4ee3677b0641f340eab85 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 13:30:30 +0100 Subject: [PATCH 006/372] tools: add style guidance and accuracy audits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .clang-format | 1 + .github/skills/code_style/SKILL.md | 171 +++++ .github/skills/implement/SKILL.md | 9 + .github/skills/scaffold/SKILL.md | 16 + AGENTS.md | 24 +- README.md | 50 ++ tools/_common.py | 15 +- tools/code_style.py | 965 +++++++++++++++++++++++++++++ tools/decomp-context.py | 23 +- tools/decomp-diff.py | 20 +- tools/decomp-workflow.py | 26 + 11 files changed, 1310 insertions(+), 10 deletions(-) create mode 100644 .github/skills/code_style/SKILL.md create mode 100644 tools/code_style.py diff --git a/.clang-format b/.clang-format index 29b540ad4..bf931d0ec 100644 --- a/.clang-format +++ b/.clang-format @@ -3,3 +3,4 @@ ColumnLimit: 150 AllowShortFunctionsOnASingleLine: Empty IndentWidth: 4 IndentCaseLabels: true +SortIncludes: Never diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md new file mode 100644 index 000000000..5aafbce35 --- /dev/null +++ b/.github/skills/code_style/SKILL.md @@ -0,0 +1,171 @@ +--- +name: code-style +description: Repo-specific code style and match-safe cleanup guidance for code-writing tasks. +--- + +# Code Style Workflow + +Use this skill when writing new code, polishing code you already touched, or doing a style review of a branch. + +This skill is about **code style only**: formatting, declaration placement, header layout, local readability, and repo-specific conventions. It is **not** a PR-response workflow and it is **not** a license to do broad cleanup sweeps. + +It also tracks the repo's written `STYLE_GUIDE.md` rules where they fit the decomp workflow. + +## Core Principle + +In this repo, style cleanup must preserve decomp progress. + +- In match-sensitive code, prefer the smallest local cleanup that keeps the code readable. +- If a style tweak changes codegen or match status, revert it. +- Extend this skill only from patterns you actually verified in the repo. + +## Quick Tooling + +Use the repo-local helper before doing a style pass: + +```sh +python tools/code_style.py audit --base origin/main +``` + +- `audit` classifies changed files into safe vs match-sensitive buckets and reports repo-specific findings. +- `audit` also checks touched `class` / `struct` declarations against known header declarations and, when no header exists, against the PS2 visibility rule. +- `audit` warns on touched local forward declarations when the repo already has a header for that type. +- `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. +- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. +- `audit` groups repeated findings by file so branch-wide output stays readable. +- Use `audit --category safe-cpp` for frontend/support cleanup passes and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. +- `format --check` is an opt-in wrapper around the repo's `.clang-format`, but it only targets safe C/C++ files by default. +- Use `format --check --base origin/main --category safe-cpp` when you want a branch-level formatter probe instead of spelling every file path out. +- `format --check` labels whitespace-only formatter output separately from more invasive changes such as include reordering. +- `format` never targets `SourceLists/z*.cpp`; those files stay audit-only even when you opt into risky formatting. +- `format` skips files that use initializer-list guard comments (`//`) unless you explicitly override that, because clang-format fights this repo-specific convention. +- `clang-format` itself is optional. If it is not on `PATH`, install it locally or point the helper at it with `CLANG_FORMAT=/path/to/clang-format`. +- Do not pass `--include-match-sensitive` unless you are deliberately taking on verification work afterwards. + +## Phase 1: Classify the File Before Cleaning + +Decide which bucket the file belongs to: + +### 1a. Match-sensitive decomp code + +Examples: + +- gameplay / camera / physics / world translation-unit source +- utility headers and templates included by matched code +- headers with layout-sensitive inlines or emitted virtual methods + +For these files, style cleanup must be conservative and verified. + +### 1b. Safer support / frontend / tooling code + +Examples: + +- frontend interface shims +- scripts, tooling, and agent docs +- non-match-critical glue code + +These files can take normal readability cleanup, but still follow repo conventions. + +## Phase 2: Apply Repo-Specific Style Rules + +### Jumbo source-list files + +- Keep deliberate blank lines between `#include` entries when they help prevent clang-format from collapsing or reshuffling the jumbo list. +- Do not leave stray helper declarations in a `SourceLists/z*.cpp` file when they really belong near a use site in the underlying implementation file. + +### Constructors and initializer lists + +- Preserve the repo's multiline initializer-list style. +- Keep the trailing `//` markers on each initializer line except the last when that pattern is already being used to keep clang-format from collapsing the list. + +Example: + +```cpp +Foo::Foo() + : a(0), // + b(1), // + c(2) {} +``` + +### Casts, nulls, and low-level code + +- Use C++ casts instead of C-style casts. +- Spell casts without spaces inside the angle brackets: `static_cast(expr)`, not `static_cast< Type * >(expr)`. +- Use `nullptr` exclusively for null pointers. +- Prefer `if (ptr)` / `if (!ptr)` over explicit null comparisons when the change is local and verified safe. +- Inline assembly is acceptable when it is needed to preserve dead-code compares, ordering, or other compiler behavior that source alone cannot reproduce. + +### Forward declarations and local prototypes + +- Prefer including the owning repo header over adding a local forward declaration for a project type. +- If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. +- Only keep a local forward declaration when no canonical repo header exists yet and you have verified that the ownership is still unresolved. +- Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. + +### Pointer style + +- Prefer `Type *name` over `Type* name`. +- Do not do broad pointer-style sweeps in match-sensitive files; change a small batch and verify the affected unit. + +### Header layout and data carriers + +- Use the repo's header guard form when writing headers: `#ifndef` / `#define` plus the `#ifdef EA_PRAGMA_ONCE_SUPPORTED` / `#pragma once` block. +- Keep member layout comments aligned and intact in decomp headers. +- Preserve the original `class` / `struct` kind from existing headers or Dwarf / PS2 evidence; do not treat it as a cosmetic style choice. +- Treat header declarations as the repo source of truth. If the repo only has local `.cpp` partial declarations, verify the kind with the PS2 dump instead of copying them blindly. +- Even forward declarations and local partial declarations should use the accurate keyword when known. +- Preserve the member naming style that DWARF shows. Some types use `mMember`, others use `m_member`; do not normalize them. +- Preserve recovered member names, types, order, and offset comments. Do not invent placeholder members named `pad`, `unk`, `unknown`, or `field_XXXX` for game code just to make a layout compile. +- If a member is genuinely unknown, stop and verify it with `find-symbol.py`, GC Dwarf, and PS2 data. If the layout is still incomplete, add a short TODO above the type instead of burying uncertainty in fake member names. +- Add offset / size comments when you are writing recovered type layouts from DWARF. +- Define inline member functions in headers only when DWARF shows that they are genuinely inlined in the binary. +- Use `struct` for POD-like data carriers with public fields; use `class` for behavior-heavy types only when that matches the recovered type information. +- Keep tiny placeholder methods as concise inline bodies when that is already the local pattern. + +### Namespaces and container aliases + +- Do not add `using` directives. +- Keep namespace-qualified types explicit at point of use. +- When introducing `UTL::Std::list` / `UTL::Std::vector` aliases that rely on a `_type_` helper, pair them with `DECLARE_CONTAINER_TYPE`. + +### Dense local code + +- Expand dense one-line helper structs, declaration blocks, and function bodies in non-match-sensitive files into normal multiline formatting. +- Prefer readable blocks over stacked one-line statements when behavior does not depend on exact source shape. + +### Uncertain ownership + +- If a declaration or global clearly compiles but its original home is uncertain, add a short TODO comment instead of inventing structure you cannot justify yet. +- When ownership matters, verify it with `decomp-workflow.py`, `decomp-context.py`, and `line-lookup` before moving code. + +## Phase 3: Things Not To "Clean Up" Blindly + +- Do not move an inline method out of a header just because it looks cleaner. +- Do not broad-format utility templates or virtual interfaces without checking who includes them. +- Do not rewrite expression structure in a near-matching function just to satisfy a style preference. +- Do not replace repo-specific formatting conventions with generic modern C++ preferences. + +## Phase 4: Verify Risky Style Changes + +For match-sensitive translation units: + +```sh +python tools/decomp-workflow.py build -u main/Path/To/TU +python tools/decomp-status.py --unit main/Path/To/TU +``` + +For safer but still compiled code: + +```sh +python tools/decomp-workflow.py build -u main/Path/To/OtherTU +``` + +Keep the cleanup only if the build succeeds and the relevant match status is unchanged. + +## Branch Patterns Confirmed So Far + +- Blank-line spacing in jumbo source-list include blocks is intentional and worth preserving. +- Helper template declarations should live near the file that actually uses them, not in the jumbo source-list file. +- The trailing `//` initializer-list markers are an intentional repo convention, not noise to remove. +- Small `if (ptr)` cleanup batches can be kept in match-sensitive code, but only after rebuilding the affected unit. +- Dense frontend shim files benefit from multiline struct/prototype/function formatting. diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index a81bb47c1..95ad95016 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -24,6 +24,11 @@ functions unless the user explicitly wants a cleanup/refiner pass. Use the wrapper flow first throughout this skill. Drop to raw `decomp-context.py` or `decomp-diff.py` only when the wrapper is missing a specific flag or you are debugging. +Before doing any local readability/style cleanup in code you are editing, consult +`.github/skills/code_style/SKILL.md`. Follow it for formatting, declaration placement, +pointer-style cleanup, and match-safe polish. Do not trade away match behavior for a +style preference. + ### 1a. decomp-context.py Preferred shortcut: @@ -70,6 +75,10 @@ Reference the skill for the usage. It gives info based on the virtual address of - Read the headers for class layout, member types, field offsets and the source files for existing implementations and includes (both are in `src/.../*.cpp`). - Check parent class headers for inherited members/methods used in the function +- Before adding any new declaration, partial declaration, or forward declaration, check whether the type already exists with `python tools/find-symbol.py `. +- If a repo header already exists for the type, include that header instead of introducing a local forward declaration. +- Preserve the original `class` vs `struct` kind. If the existing header is missing or incomplete, verify the type kind from GC Dwarf and PS2 info before writing a local declaration. +- Preserve real member names and field types too. Do not introduce `pad`, `unk`, or `field_XXXX` members as placeholders for guessed layout; verify the member list from GC Dwarf / PS2 data and leave a TODO when something is still uncertain. ### 1e. Assembly reference diff --git a/.github/skills/scaffold/SKILL.md b/.github/skills/scaffold/SKILL.md index 76abd7925..a2f062f50 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -31,6 +31,22 @@ Collect data from **all** of these sources in parallel where possible: Copy and cleanup the header that you got from running the `lookup` skill using the `symbols/Dwarf` folder. Fix visibility, function order and vtable related things based on using `lookup` on the PS2 types. +For formatting and local cleanup while writing the header, consult +`.github/skills/code_style/SKILL.md`. Use it for member-comment alignment, declaration +grouping, TODO placement, and other repo-specific style decisions. + +Preserve the real `class` / `struct` kind while scaffolding. Check existing headers first, +then use Dwarf plus PS2 visibility / vtable info to decide the type kind. Even temporary +forward declarations should match the known original kind. + +If the repo already has a header for a type you need, include that header instead of +adding a new local forward declaration. Only forward-declare when no canonical repo header +exists yet and you have verified that the ownership is still unresolved. + +Preserve real member names, types, order, and offset comments while scaffolding. Do not +fill gaps with invented `pad`, `unk`, or `field_XXXX` members for game types; verify the +layout from Dwarf / PS2 data and leave a TODO over the type if a field is still uncertain. + Only create headers if it's really necessary (the struct doesn't have inlines so you can't determine in which header file it goes and it's thematically very different from the other structs that use it), otherwise put it into the one you determined to be correct. The dwarf often has duplicated inlines, clean those up according to the order in the PS2 info. diff --git a/AGENTS.md b/AGENTS.md index b95d62190..0c400cca5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,6 +83,17 @@ originates from, use this script against the compiler-generated debug line mappi See `.github/skills/line_lookup/SKILL.md` for the full workflow. +### code-style — Repo-local style guidance + +When you are writing code, polishing code you already touched, or doing a style-review pass, +consult `.github/skills/code_style/SKILL.md` first. It captures repo-specific formatting and +cleanup rules, including jumbo include spacing, initializer-list comment markers, declaration +placement, pointer style, and how to keep style work safe in match-sensitive code. + +Use `python tools/code_style.py audit --base origin/main` before a branch-wide style pass. +It classifies changed files, reports repo-specific findings, and only treats safer C/C++ files +as clang-format candidates by default. + ### decomp-diff.py — Diff & symbol overview Overview mode lists all symbols in a translation unit with match status: @@ -92,10 +103,12 @@ python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -s nonmatching -t function python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -s missing -t function python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim --search RemoveIOWin +python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -d FindIOWin --reloc-diffs all ``` Filters: `-t function,object` (type), `-s missing|matching|nonmatching|extra` (status), -`--section .text`, `--search ` (fuzzy name match). +`--section .text`, `--search ` (fuzzy name match), `--reloc-diffs none|name_address|data_value|all` +(surface relocation-only mismatches when needed; default: `none`). Diff mode shows side-by-side instruction comparison: @@ -262,9 +275,14 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho - No `auto`, range-for, `enum class`, lambdas, or any C++11+ - Enum values use prefix: `enum EFoo { kF_Value1, kF_Value2 }` (not `enum class`) -- Use C++ casts (`static_cast< T >(expr)`) instead of C-style casts -- Header guards: `#ifndef _CLASSNAME` / `#define _CLASSNAME` (not `#pragma once`) +- Use C++ casts (`static_cast(expr)`) instead of C-style casts +- Header guards should use `#ifndef` / `#define` together with the `EA_PRAGMA_ONCE_SUPPORTED` block when writing repo headers - Constructors use initializer list style with leading `, ` on each line, add empty comments at the end of these lines (except the last) to stop clang-format from putting them all on the same line +- Inline assembly is acceptable when needed to reproduce dead code or compiler scheduling that source alone cannot express cleanly +- Preserve the original `class` vs `struct` kind. Check existing headers first, then Dwarf / PS2 info when needed. Even forward declarations and local partial declarations should use the accurate keyword when known. +- Prefer including the real repo header over introducing a local forward declaration for a project type. If a type already has a header in `src/`, include it instead of redeclaring it locally. +- Preserve original member names, types, order, and proven layout comments. Do not invent `pad`, `unk`, or `field_XXXX` members just to satisfy a guessed size or offset; verify the real members with `find-symbol.py`, GC Dwarf, and PS2 data, and leave a short TODO if a layout detail is still uncertain. +- Follow DWARF member naming exactly (`mMember` vs `m_member`) instead of normalizing names - Omit the `this` pointer. - Use `nullptr` and `override`. If they are missing, you need to include `types.h`. - Omit `struct` when declaring variables or parameters, we are not in C land. diff --git a/README.md b/README.md index b10d186c0..e36bd8a4e 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,56 @@ This file contains bChunk chunk IDs. Just tell your favourite clanker to reference `AGENTS.md` to decompile a translation unit of your choice, for example `main/Speed/Indep/SourceLists/zEAXSound`. +When introducing or forward-declaring a type, preserve the original `class` / `struct` +kind. Check existing headers first with `python tools/find-symbol.py `, then use +GC Dwarf and PS2 type info when the real declaration is missing or incomplete. + +Preserve real member names, types, order, and offset comments too. For recovered game +types, do not invent `pad`, `unk`, or `field_XXXX` members to force a guessed layout; use +the debug data and leave a short TODO when a field is still unresolved. + +If a project type already has a header in `src/`, include that header instead of adding a +local forward declaration. + +## Style tooling + +The repo ships with a decomp-aware style helper: + +```sh +python tools/code_style.py audit --base origin/main +``` + +Use `audit` to classify branch changes into safer vs match-sensitive buckets and to flag repo-specific issues such as jumbo include spacing, stray top-level declarations in `SourceLists` files, touched `class` / `struct` declarations that disagree with known headers or the PS2 visibility rule, touched project forward declarations that should be replaced by real includes, touched type members that look like invented padding or placeholder names, and touched style-guide issues that clang-format cannot fix for you (`using namespace`, `NULL`, bad cast spacing, or missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks). +Repeated findings are grouped by file so large branch audits stay readable. + +Useful focused passes: + +```sh +python tools/code_style.py audit --base origin/main --category safe-cpp +python tools/code_style.py audit --base origin/main --category match-sensitive-cpp +python tools/code_style.py format --check --base origin/main --category safe-cpp +``` + +If you have `clang-format` installed locally, you can also use: + +```sh +python tools/code_style.py format --check src/Speed/Indep/Src/Frontend/FEManager.cpp +``` + +The formatter wrapper only targets safer C/C++ files by default. It intentionally skips match-sensitive code unless you explicitly pass `--include-match-sensitive` and verify the affected unit afterwards. +`SourceLists/z*.cpp` files remain audit-only and are never formatter targets. +`format --check` now distinguishes whitespace-only formatter deltas from more invasive output such as include reordering. +Files that use the repo's initializer-list guard comments (`//`) are skipped by default because clang-format fights that convention; override only if you are deliberately inspecting that output. +For declaration-kind checks, header declarations are treated as the repo source of truth; otherwise the helper falls back to the PS2 dump rule (`public:` / `private:` / `protected:` means `class`, no visibility labels means `struct`). + +`clang-format` is optional. Recommended installs: + +- macOS: `brew install clang-format` +- Linux: `sudo apt install clang-format` +- Windows: `winget install LLVM.LLVM` + +If your binary lives outside `PATH`, set `CLANG_FORMAT` to the executable path before running `tools/code_style.py format`. + # Contributors Special thanks to [Brawltendo](https://github.com/Brawltendo) for helping with tooling and letting me use his partial decomp. diff --git a/tools/_common.py b/tools/_common.py index db992bd8b..d1b4f42a0 100644 --- a/tools/_common.py +++ b/tools/_common.py @@ -14,9 +14,8 @@ ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) BUILD_NINJA = os.path.join(ROOT_DIR, "build.ninja") OBJDIFF_JSON = os.path.join(ROOT_DIR, "objdiff.json") +RELOC_DIFF_CHOICES = ("none", "name_address", "data_value", "all") OBJDIFF_DEFAULT_CONFIG_ARGS = [ - "-c", - "functionRelocDiffs=none", "-c", "ppc.calculatePoolRelocations=false", ] @@ -55,6 +54,15 @@ def ensure_project_prereqs(require_build_ninja: bool = False) -> None: ensure_exists(BUILD_NINJA, "Run: python configure.py") +def build_objdiff_config_args(reloc_diffs: str = "none") -> List[str]: + if reloc_diffs not in RELOC_DIFF_CHOICES: + raise ToolError( + f"Invalid relocation diff mode: {reloc_diffs} " + f"(expected one of {', '.join(RELOC_DIFF_CHOICES)})" + ) + return ["-c", f"functionRelocDiffs={reloc_diffs}", *OBJDIFF_DEFAULT_CONFIG_ARGS] + + def load_json_file(path: str, description: str) -> Any: try: with open(path) as f: @@ -262,12 +270,13 @@ def run_objdiff_json( *, base_obj: Optional[str] = None, extra_args: Optional[Sequence[str]] = None, + reloc_diffs: str = "none", root_dir: str = ROOT_DIR, ) -> Dict[str, Any]: ensure_project_prereqs() cmd = [objdiff_cli, "diff"] - cmd.extend(OBJDIFF_DEFAULT_CONFIG_ARGS) + cmd.extend(build_objdiff_config_args(reloc_diffs)) if extra_args: cmd.extend(extra_args) cmd.extend(["-u", unit_name, "-o", "-", "--format", "json"]) diff --git a/tools/code_style.py b/tools/code_style.py new file mode 100644 index 000000000..1f14f7c72 --- /dev/null +++ b/tools/code_style.py @@ -0,0 +1,965 @@ +#!/usr/bin/env python3 +""" +Decomp-aware code style helper. + +Examples: + python tools/code_style.py audit --base origin/main + python tools/code_style.py classify src/Speed/Indep/Src/Frontend/FEManager.cpp + python tools/code_style.py format --check src/Speed/Indep/Src/Frontend/FEManager.cpp +""" + +import argparse +import os +import platform +import re +import shutil +import subprocess +import sys +from dataclasses import dataclass +from typing import Dict, Iterable, List, Optional, Sequence, Set + +script_dir = os.path.dirname(os.path.realpath(__file__)) +root_dir = os.path.abspath(os.path.join(script_dir, "..")) +src_dir = os.path.join(root_dir, "src") +ps2_types_path = os.path.join(root_dir, "symbols", "PS2", "PS2_types.nothpp") + +CPP_EXTS = {".c", ".cc", ".cpp", ".h", ".hh", ".hpp"} +HEADER_EXTS = {".h", ".hh", ".hpp"} + +SAFE_CPP_PREFIXES = ( + "src/Speed/Indep/Src/Frontend/", + "src/Speed/Indep/Src/FEng/", +) +DOC_PREFIXES = ( + ".github/skills/", + "docs/", +) +TOOL_PREFIXES = ( + "tools/", +) +JUMBO_PREFIX = "src/Speed/Indep/SourceLists/" +MATCH_SENSITIVE_PREFIXES = ( + "src/Speed/Indep/Libs/Support/Utility/", + "src/Speed/Indep/bWare/Inc/", + "src/Speed/Indep/Src/", +) +ROOT_FILES = { + "AGENTS.md", + "README.md", +} +CATEGORIES = ( + "docs", + "jumbo-source-list", + "match-sensitive-cpp", + "match-sensitive-other", + "other", + "safe-cpp", + "safe-other", + "tooling", +) + + +@dataclass +class Finding: + path: str + line: int + severity: str + message: str + + +DECL_PATTERN = re.compile( + r"^\s*(struct|class)\s+([A-Za-z_][A-Za-z0-9_]*)\b(?:\s*[:;{]|$)" +) +TYPE_BODY_START_PATTERN = re.compile(r"^\s*(struct|class)\s+([A-Za-z_][A-Za-z0-9_]*)\b.*\{") +FORWARD_DECL_PATTERN = re.compile(r"^\s*(struct|class)\s+([A-Za-z_][A-Za-z0-9_]*)\s*;\s*$") +VISIBILITY_PATTERN = re.compile(r"^\s*(public|private|protected)\s*:", re.MULTILINE) +ACCESS_SPECIFIER_PATTERN = re.compile(r"^\s*(public|private|protected)\s*:\s*$") +CAST_SPACING_PATTERN = re.compile( + r"\b(?:static_cast|reinterpret_cast|const_cast|dynamic_cast)\s*<\s+" + r"|" + r"\b(?:static_cast|reinterpret_cast|const_cast|dynamic_cast)\s*<[^>\n]*\s+>" +) +USING_NAMESPACE_PATTERN = re.compile(r"^\s*using\s+namespace\b") +NULL_PATTERN = re.compile(r"\bNULL\b") +HEADER_GUARD_IFNDEF_PATTERN = re.compile(r"^\s*#ifndef\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) +HEADER_GUARD_DEFINE_PATTERN = re.compile(r"^\s*#define\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) +EA_PRAGMA_BLOCK_PATTERN = re.compile( + r"^\s*#ifdef\s+EA_PRAGMA_ONCE_SUPPORTED\s*$" + r".*?^\s*#pragma\s+once\s*$" + r".*?^\s*#endif\s*$", + re.MULTILINE | re.DOTALL, +) +SUSPICIOUS_MEMBER_PATTERN = re.compile( + r"^(?:" + r"_?pad(?:ding)?[0-9A-Fa-f_]*" + r"|pad(?:byte|char)" + r"|unk(?:nown)?[0-9A-Fa-f_]*" + r"|unk_[A-Za-z0-9_]+" + r"|field_[0-9A-Fa-f]+" + r")$" +) + +_source_decl_cache: Optional[Dict[str, List[tuple]]] = None +_ps2_kind_cache: Dict[str, Optional[str]] = {} + + +def run_git(args: Sequence[str]) -> str: + result = subprocess.run( + ["git", *args], + cwd=root_dir, + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise RuntimeError(result.stderr.strip() or "git command failed") + return result.stdout + + +def relpath(path: str) -> str: + abs_path = path if os.path.isabs(path) else os.path.join(root_dir, path) + return os.path.relpath(abs_path, root_dir).replace("\\", "/") + + +def path_category(path: str) -> str: + path = relpath(path) + ext = os.path.splitext(path)[1] + + if path in ROOT_FILES: + return "docs" + if any(path.startswith(prefix) for prefix in DOC_PREFIXES): + return "docs" + if any(path.startswith(prefix) for prefix in TOOL_PREFIXES): + return "tooling" + if path.startswith(JUMBO_PREFIX): + return "jumbo-source-list" + if any(path.startswith(prefix) for prefix in SAFE_CPP_PREFIXES): + return "safe-cpp" if ext in CPP_EXTS else "safe-other" + if any(path.startswith(prefix) for prefix in MATCH_SENSITIVE_PREFIXES): + return "match-sensitive-cpp" if ext in CPP_EXTS else "match-sensitive-other" + return "other" + + +def file_candidates_from_base(base: str, include_worktree: bool) -> List[str]: + files: Set[str] = set() + for line in run_git(["diff", "--name-only", f"{base}...HEAD"]).splitlines(): + if line.strip(): + files.add(line.strip()) + if include_worktree: + for line in run_git(["diff", "--name-only"]).splitlines(): + if line.strip(): + files.add(line.strip()) + return sorted(files) + + +def collect_touched_lines_from_diff(diff_text: str) -> Dict[str, Set[int]]: + touched: Dict[str, Set[int]] = {} + current_path: Optional[str] = None + + for line in diff_text.splitlines(): + if line.startswith("+++ b/"): + current_path = line[6:] + touched.setdefault(current_path, set()) + continue + + if not line.startswith("@@") or current_path is None: + continue + + match = re.search(r"\+(\d+)(?:,(\d+))?", line) + if match is None: + continue + + start = int(match.group(1)) + count = int(match.group(2) or "1") + if count == 0: + continue + + for line_no in range(start, start + count): + touched.setdefault(current_path, set()).add(line_no) + + return touched + + +def touched_lines_from_base(base: str, include_worktree: bool) -> Dict[str, Set[int]]: + touched = collect_touched_lines_from_diff( + run_git(["diff", "--unified=0", f"{base}...HEAD"]) + ) + if include_worktree: + worktree_touched = collect_touched_lines_from_diff( + run_git(["diff", "--unified=0"]) + ) + for path, lines in worktree_touched.items(): + touched.setdefault(path, set()).update(lines) + return touched + + +def read_text(path: str) -> str: + with open( + os.path.join(root_dir, relpath(path)), + encoding="utf-8", + errors="ignore", + ) as f: + return f.read() + + +def source_declaration_index() -> Dict[str, List[tuple]]: + global _source_decl_cache + if _source_decl_cache is not None: + return _source_decl_cache + + index: Dict[str, List[tuple]] = {} + for dirpath, _dirs, files in os.walk(src_dir): + for fname in files: + if os.path.splitext(fname)[1] not in CPP_EXTS: + continue + fpath = os.path.join(dirpath, fname) + rel = os.path.relpath(fpath, root_dir).replace("\\", "/") + try: + with open(fpath, encoding="utf-8", errors="ignore") as f: + for lineno, line in enumerate(f, 1): + match = DECL_PATTERN.match(line) + if match is None: + continue + kind = match.group(1) + name = match.group(2) + index.setdefault(name, []).append((kind, rel, lineno)) + except OSError: + continue + + _source_decl_cache = index + return index + + +def expected_kind_from_source(name: str, current_path: str, current_line: int) -> Optional[str]: + candidates = source_declaration_index().get(name, []) + filtered = [] + for kind, rel, lineno in candidates: + if rel == current_path and lineno == current_line: + continue + if os.path.splitext(rel)[1] not in {".h", ".hh", ".hpp"}: + continue + filtered.append(kind) + unique = sorted(set(filtered)) + if len(unique) == 1: + return unique[0] + return None + + +def header_declaration_paths(name: str, current_path: str, current_line: int) -> List[str]: + candidates = source_declaration_index().get(name, []) + headers = set() + for _kind, rel, lineno in candidates: + if rel == current_path and lineno == current_line: + continue + if os.path.splitext(rel)[1] not in {".h", ".hh", ".hpp"}: + continue + headers.add(rel) + return sorted(headers) + + +def expected_kind_from_ps2(name: str) -> Optional[str]: + cached = _ps2_kind_cache.get(name) + if cached is not None or name in _ps2_kind_cache: + return cached + + if not os.path.isfile(ps2_types_path): + _ps2_kind_cache[name] = None + return None + + result = subprocess.run( + ["python", "tools/lookup.py", "--file", ps2_types_path, "struct", name], + cwd=root_dir, + capture_output=True, + text=True, + ) + output = (result.stdout or result.stderr).strip() + if result.returncode != 0 or not output.startswith("struct "): + _ps2_kind_cache[name] = None + return None + + if VISIBILITY_PATTERN.search(output): + _ps2_kind_cache[name] = "class" + else: + _ps2_kind_cache[name] = "struct" + return _ps2_kind_cache[name] + + +def audit_type_kind_declarations( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings: List[Finding] = [] + for idx, line in enumerate(text.splitlines(), 1): + if touched_lines is not None and idx not in touched_lines: + continue + match = DECL_PATTERN.match(line) + if match is None: + continue + + actual_kind = match.group(1) + name = match.group(2) + expected_kind = expected_kind_from_source(name, path, idx) + reason = "repo declaration" + if expected_kind is None: + expected_kind = expected_kind_from_ps2(name) + reason = "PS2 visibility rule" + if expected_kind is None or expected_kind == actual_kind: + continue + + findings.append( + Finding( + path, + idx, + "WARN", + f"`{actual_kind} {name}` disagrees with known type kind; use `{expected_kind} {name}` ({reason})", + ) + ) + return findings + + +def extract_member_name(line: str) -> Optional[str]: + code = line.split("//", 1)[0].strip() + if not code or code.startswith("#") or code.endswith(":"): + return None + if "(" in code or ")" in code: + return None + if any(code.startswith(prefix) for prefix in ("typedef ", "using ", "enum ", "union ", "class ", "struct ", "friend ")): + return None + + code = code.rstrip(";").strip() + if "," in code: + return None + if "=" in code: + code = code.split("=", 1)[0].rstrip() + + match = re.search( + r"([A-Za-z_][A-Za-z0-9_]*)\s*(?:\[[^\]]+\])?\s*(?::\s*\d+)?\s*$", + code, + ) + if match is None: + return None + return match.group(1) + + +def audit_placeholder_members( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings: List[Finding] = [] + current_type: Optional[str] = None + pending_type: Optional[str] = None + brace_depth = 0 + + for idx, line in enumerate(text.splitlines(), 1): + stripped = line.strip() + + if current_type is None: + start_match = TYPE_BODY_START_PATTERN.match(line) + if start_match is not None: + current_type = start_match.group(2) + brace_depth = line.count("{") - line.count("}") + if brace_depth <= 0: + current_type = None + brace_depth = 0 + continue + + decl_match = DECL_PATTERN.match(line) + if decl_match is not None and "{" not in line and not stripped.endswith(";"): + pending_type = decl_match.group(2) + + if pending_type is not None and "{" in line: + current_type = pending_type + pending_type = None + brace_depth = line.count("{") - line.count("}") + if brace_depth <= 0: + current_type = None + brace_depth = 0 + continue + + if stripped.endswith(";"): + pending_type = None + continue + + if touched_lines is None or idx in touched_lines: + if not ACCESS_SPECIFIER_PATTERN.match(line): + member_name = extract_member_name(line) + if member_name is not None and SUSPICIOUS_MEMBER_PATTERN.match(member_name): + findings.append( + Finding( + path, + idx, + "WARN", + f"`{current_type}` member `{member_name}` looks like placeholder padding/unknown naming; verify the real member from Dwarf/PS2 instead of inventing pads", + ) + ) + + brace_depth += line.count("{") - line.count("}") + if brace_depth <= 0: + current_type = None + brace_depth = 0 + + return findings + + +def audit_forward_declarations( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings: List[Finding] = [] + for idx, line in enumerate(text.splitlines(), 1): + if touched_lines is not None and idx not in touched_lines: + continue + match = FORWARD_DECL_PATTERN.match(line) + if match is None: + continue + + name = match.group(2) + headers = header_declaration_paths(name, path, idx) + if not headers: + continue + + sample = ", ".join(headers[:2]) + if len(headers) > 2: + sample += ", ..." + findings.append( + Finding( + path, + idx, + "WARN", + f"`{name}` is forward-declared even though repo headers exist; include {sample} instead of redeclaring", + ) + ) + return findings + + +def audit_style_guide_rules( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings: List[Finding] = [] + ext = os.path.splitext(path)[1] + + for idx, line in enumerate(text.splitlines(), 1): + if touched_lines is not None and idx not in touched_lines: + continue + stripped = line.strip() + if stripped.startswith("//"): + continue + + if CAST_SPACING_PATTERN.search(line): + findings.append( + Finding( + path, + idx, + "WARN", + "C++ cast uses spaces inside `<...>`; prefer `static_cast(expr)` style", + ) + ) + if USING_NAMESPACE_PATTERN.search(line): + findings.append( + Finding( + path, + idx, + "WARN", + "`using namespace` is not allowed here; keep names fully qualified", + ) + ) + if NULL_PATTERN.search(line): + findings.append( + Finding( + path, + idx, + "WARN", + "use `nullptr` instead of `NULL`", + ) + ) + + if ext in HEADER_EXTS: + should_check_guard = touched_lines is None or any(line_no <= 8 for line_no in touched_lines) + if should_check_guard: + has_ifndef = HEADER_GUARD_IFNDEF_PATTERN.search(text) is not None + has_define = HEADER_GUARD_DEFINE_PATTERN.search(text) is not None + has_pragma_block = EA_PRAGMA_BLOCK_PATTERN.search(text) is not None + if not (has_ifndef and has_define and has_pragma_block): + findings.append( + Finding( + path, + 1, + "WARN", + "header guard should use `#ifndef` / `#define` plus the `EA_PRAGMA_ONCE_SUPPORTED` `#pragma once` block", + ) + ) + + return findings + + +def audit_source_list( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings: List[Finding] = [] + lines = text.splitlines() + seen_include = False + prev_include_line = -1 + + for idx, line in enumerate(lines, 1): + stripped = line.strip() + if not seen_include: + if stripped.startswith("#include "): + seen_include = True + prev_include_line = idx + continue + + if stripped.startswith("#include "): + if idx == prev_include_line + 1 and ( + touched_lines is None + or idx in touched_lines + or prev_include_line in touched_lines + ): + findings.append( + Finding( + path, + idx, + "WARN", + "consecutive jumbo includes without a separating blank line", + ) + ) + prev_include_line = idx + continue + + if stripped == "": + continue + + if touched_lines is None or idx in touched_lines: + findings.append( + Finding( + path, + idx, + "INFO", + "top-level declaration/code in SourceLists file; keep only if placement is intentional", + ) + ) + break + + return findings + + +def audit_safe_cpp( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings = audit_type_kind_declarations(path, text, touched_lines) + findings.extend(audit_forward_declarations(path, text, touched_lines)) + findings.extend(audit_placeholder_members(path, text, touched_lines)) + findings.extend(audit_style_guide_rules(path, text, touched_lines)) + pointer_pattern = re.compile( + r"\b[A-Za-z_][A-Za-z0-9_:<>]*\*\s*[A-Za-z_][A-Za-z0-9_]*" + ) + + for idx, line in enumerate(text.splitlines(), 1): + stripped = line.strip() + if stripped.startswith("//") or stripped.startswith("#"): + continue + if touched_lines is not None and idx not in touched_lines: + continue + if pointer_pattern.search(line): + findings.append( + Finding( + path, + idx, + "INFO", + "pointer declaration/prototype uses `Type* name`; repo style prefers `Type *name`", + ) + ) + return findings + + +def audit_match_sensitive_cpp( + path: str, text: str, touched_lines: Optional[Set[int]] +) -> List[Finding]: + findings = audit_type_kind_declarations(path, text, touched_lines) + findings.extend(audit_forward_declarations(path, text, touched_lines)) + findings.extend(audit_placeholder_members(path, text, touched_lines)) + findings.extend(audit_style_guide_rules(path, text, touched_lines)) + nullptr_pattern = re.compile(r"\bif\s*\([^)]*(?:==|!=)\s*nullptr") + + for idx, line in enumerate(text.splitlines(), 1): + if touched_lines is not None and idx not in touched_lines: + continue + if nullptr_pattern.search(line): + findings.append( + Finding( + path, + idx, + "INFO", + "pointer-null comparison is a candidate for `if (ptr)` cleanup, but verify the affected TU first", + ) + ) + return findings + + +def audit_path(path: str, touched_lines: Optional[Set[int]]) -> List[Finding]: + path = relpath(path) + abs_path = os.path.join(root_dir, path) + if not os.path.isfile(abs_path): + return [] + + category = path_category(path) + text = read_text(path) + + if category == "jumbo-source-list": + return audit_source_list(path, text, touched_lines) + if category == "safe-cpp": + return audit_safe_cpp(path, text, touched_lines) + if category == "match-sensitive-cpp": + return audit_match_sensitive_cpp(path, text, touched_lines) + return [] + + +def gather_paths(args: argparse.Namespace) -> List[str]: + if args.paths: + return [relpath(path) for path in args.paths] + return file_candidates_from_base(args.base, include_worktree=not args.no_worktree) + + +def filter_paths_by_category( + paths: Iterable[str], categories: Optional[Sequence[str]] +) -> List[str]: + if not categories: + return list(paths) + allowed = set(categories) + return [path for path in paths if path_category(path) in allowed] + + +def format_line_list(lines: Sequence[int], sample_limit: int) -> str: + sample = list(lines[:sample_limit]) + rendered = ", ".join(str(line) for line in sample) + if len(lines) > sample_limit: + rendered += ", ..." + return rendered + + +def strip_whitespace(text: str) -> str: + return re.sub(r"\s+", "", text) + + +def include_lines(text: str) -> List[str]: + return [line.strip() for line in text.splitlines() if line.strip().startswith("#include ")] + + +def has_initializer_guard_comments(text: str) -> bool: + guard_pattern = re.compile(r"^\s*(?::|,)\s+.*//\s*$", re.MULTILINE) + return guard_pattern.search(text) is not None + + +def format_change_summary(before: str, after: str) -> str: + reasons: List[str] = [] + if strip_whitespace(before) == strip_whitespace(after): + reasons.append("whitespace-only") + else: + reasons.append("non-whitespace token/order changes") + + before_includes = include_lines(before) + after_includes = include_lines(after) + if before_includes and before_includes != after_includes and sorted(before_includes) == sorted(after_includes): + reasons.append("reorders includes") + + if has_initializer_guard_comments(before): + reasons.append("initializer-list guard comments present") + + return ", ".join(reasons) + + +def command_classify(args: argparse.Namespace) -> int: + for path in filter_paths_by_category(gather_paths(args), args.category): + print(f"{path_category(path):<22} {path}") + return 0 + + +def command_audit(args: argparse.Namespace) -> int: + paths = filter_paths_by_category(gather_paths(args), args.category) + if not paths: + print("No files selected.") + return 0 + + touched_lines = None if args.paths else touched_lines_from_base( + args.base, include_worktree=not args.no_worktree + ) + + by_category = {} + findings: List[Finding] = [] + for path in paths: + by_category.setdefault(path_category(path), []).append(path) + findings.extend( + audit_path(path, None if touched_lines is None else touched_lines.get(path)) + ) + + print("File categories:") + for category in sorted(by_category): + print(f" {category}: {len(by_category[category])}") + print() + + safe_format_candidates = [ + path + for path in paths + if path_category(path) == "safe-cpp" and os.path.splitext(path)[1] in CPP_EXTS + ] + if safe_format_candidates: + print("Safe clang-format candidates:") + for path in safe_format_candidates: + print(f" {path}") + print() + + if not findings: + print("No style findings.") + return 0 + + print("Findings:") + findings = sorted(findings, key=lambda item: (item.path, item.line, item.message)) + if args.ungrouped: + shown = findings[: args.max_findings] + for finding in shown: + print( + f" {finding.severity:<4} {finding.path}:{finding.line}: {finding.message}" + ) + if len(findings) > len(shown): + print() + print(f" ... {len(findings) - len(shown)} more finding(s) omitted") + return 0 + + grouped: Dict[tuple, List[int]] = {} + for finding in findings: + grouped.setdefault( + (finding.severity, finding.path, finding.message), [] + ).append(finding.line) + + grouped_items = sorted(grouped.items(), key=lambda item: (item[0][1], item[1][0], item[0][2])) + shown = grouped_items[: args.max_findings] + for (severity, path, message), lines in shown: + print( + f" {severity:<4} {path}: {message} ({len(lines)} occurrence(s); lines {format_line_list(lines, args.sample_lines)})" + ) + if len(grouped_items) > len(shown): + print() + print(f" ... {len(grouped_items) - len(shown)} more grouped finding(s) omitted") + return 0 + + +def find_clang_format() -> str: + env_override = os.environ.get("CLANG_FORMAT") + if env_override: + if os.path.isfile(env_override) and os.access(env_override, os.X_OK): + return env_override + resolved = shutil.which(env_override) + if resolved is not None: + return resolved + raise RuntimeError( + f"CLANG_FORMAT is set to '{env_override}', but that executable was not found." + ) + + candidates = ( + "clang-format", + "clang-format-19", + "clang-format-18", + "clang-format-17", + "clang-format-16", + "clang-format-15", + "clang-format-14", + ) + for candidate in candidates: + resolved = shutil.which(candidate) + if resolved is not None: + return resolved + + system = platform.system() + if system == "Darwin": + install_hint = "Install it with `brew install clang-format`." + elif system == "Linux": + install_hint = "Install it with your package manager, for example `sudo apt install clang-format`." + elif system == "Windows": + install_hint = "Install LLVM/clang-format, for example with `winget install LLVM.LLVM`." + else: + install_hint = "Install clang-format and ensure it is available on PATH." + + raise RuntimeError( + "clang-format not found. " + + install_hint + + " You can also point the helper at a specific binary with the CLANG_FORMAT environment variable." + ) + + +def format_paths(paths: Iterable[str], include_match_sensitive: bool) -> List[str]: + allowed = {"safe-cpp"} + if include_match_sensitive: + allowed.add("match-sensitive-cpp") + + return [ + relpath(path) + for path in paths + if path_category(path) in allowed and os.path.splitext(path)[1] in CPP_EXTS + ] + + +def command_format(args: argparse.Namespace) -> int: + selected = format_paths( + filter_paths_by_category(gather_paths(args), args.category), + args.include_match_sensitive, + ) + if not selected: + print("No format-eligible files selected.") + return 0 + + clang_format = find_clang_format() + changed: List[str] = [] + changed_summaries: Dict[str, str] = {} + skipped_initializer_guards: List[str] = [] + + for path in selected: + abs_path = os.path.join(root_dir, path) + with open(abs_path, encoding="utf-8", errors="ignore") as f: + before = f.read() + + if has_initializer_guard_comments(before) and not args.include_initializer_guards: + skipped_initializer_guards.append(path) + continue + + if args.check: + result = subprocess.run( + [clang_format, "--style=file", abs_path], + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(result.stderr.strip(), file=sys.stderr) + return result.returncode + if result.stdout != before: + changed.append(path) + changed_summaries[path] = format_change_summary(before, result.stdout) + else: + result = subprocess.run([clang_format, "-i", "--style=file", abs_path]) + if result.returncode != 0: + return result.returncode + changed.append(path) + + if skipped_initializer_guards: + print("Skipped files with initializer-list guard comments:") + for path in skipped_initializer_guards: + print(f" {path}") + print(" clang-format fights this repo convention; inspect these manually or override explicitly.") + print() + + if args.check: + if changed: + print("Would reformat:") + for path in changed: + print(f" {path} [{changed_summaries[path]}]") + return 1 + print("All selected files already match clang-format output.") + return 0 + + print("Formatted files:") + for path in changed: + print(f" {path}") + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Decomp-aware code style helper") + subparsers = parser.add_subparsers(dest="command", required=True) + + shared = argparse.ArgumentParser(add_help=False) + shared.add_argument( + "paths", + nargs="*", + help="Files to inspect. If omitted, use changed files against --base.", + ) + shared.add_argument( + "--base", + default="origin/main", + help="Base ref used when paths are omitted (default: origin/main)", + ) + shared.add_argument( + "--no-worktree", + action="store_true", + help="Ignore uncommitted worktree changes when collecting default files", + ) + + classify = subparsers.add_parser( + "classify", + parents=[shared], + help="Classify files by style-risk bucket", + ) + classify.add_argument( + "--category", + action="append", + choices=CATEGORIES, + help="Restrict output to one or more categories", + ) + classify.set_defaults(func=command_classify) + + audit = subparsers.add_parser( + "audit", + parents=[shared], + help="Audit files for repo-specific style issues", + ) + audit.add_argument( + "--category", + action="append", + choices=CATEGORIES, + help="Restrict the audit to one or more categories", + ) + audit.add_argument( + "--max-findings", + type=int, + default=60, + help="Maximum number of findings or grouped findings to print (default: 60)", + ) + audit.add_argument( + "--sample-lines", + type=int, + default=5, + help="Maximum line samples to print per grouped finding (default: 5)", + ) + audit.add_argument( + "--ungrouped", + action="store_true", + help="Print individual findings instead of grouped summaries", + ) + audit.set_defaults(func=command_audit) + + fmt = subparsers.add_parser( + "format", + parents=[shared], + help="Run clang-format on safe files by default", + ) + fmt.add_argument( + "--category", + action="append", + choices=CATEGORIES, + help="Restrict the format pass to one or more categories", + ) + fmt.add_argument( + "--check", + action="store_true", + help="Report files that would change instead of formatting them", + ) + fmt.add_argument( + "--include-match-sensitive", + action="store_true", + help="Also format match-sensitive C/C++ files (dangerous; verify afterwards). SourceLists files stay excluded.", + ) + fmt.add_argument( + "--include-initializer-guards", + action="store_true", + help="Also format files that use initializer-list guard comments (`//`). Disabled by default because clang-format fights that repo convention.", + ) + fmt.set_defaults(func=command_format) + + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + try: + return args.func(args) + except RuntimeError as exc: + print(f"Error: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/decomp-context.py b/tools/decomp-context.py index 32a35fcae..87e486552 100644 --- a/tools/decomp-context.py +++ b/tools/decomp-context.py @@ -26,6 +26,7 @@ from typing import Any, Dict, List, Optional, Tuple from _common import ( ROOT_DIR, + RELOC_DIFF_CHOICES, ToolError, build_objdiff_symbol_rows, fail, @@ -69,11 +70,16 @@ def find_unit(config: Dict[str, Any], unit_name: str) -> Optional[Dict[str, Any] return None -def run_objdiff(unit_name: str, base_obj: Optional[str] = None) -> Optional[Dict[str, Any]]: +def run_objdiff( + unit_name: str, + base_obj: Optional[str] = None, + reloc_diffs: str = "none", +) -> Optional[Dict[str, Any]]: return run_objdiff_json( OBJDIFF_CLI, unit_name, base_obj=base_obj, + reloc_diffs=reloc_diffs, root_dir=root_dir, ) @@ -1105,6 +1111,15 @@ def main(): "Use this .o file as the decomp base instead of the one from objdiff.json." ), ) + parser.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help=( + "Control relocation-only mismatches in objdiff " + "(default: none; use all to surface relocation diffs)" + ), + ) args = parser.parse_args() if args.ghidra_check: @@ -1124,7 +1139,9 @@ def main(): source_path = meta.get("source_path", "") # === objdiff Status (run first so we have line numbers for source scoping) === - diff_data = run_objdiff(args.unit, base_obj=args.base_obj) + diff_data = run_objdiff( + args.unit, base_obj=args.base_obj, reloc_diffs=args.reloc_diffs + ) left_sym = right_sym = None if diff_data: @@ -1269,6 +1286,8 @@ def main(): args.unit, "-d", args.function, + "--reloc-diffs", + args.reloc_diffs, ] if args.base_obj: diff_cmd += ["--base-obj", args.base_obj] diff --git a/tools/decomp-diff.py b/tools/decomp-diff.py index 9f4653147..78017385e 100644 --- a/tools/decomp-diff.py +++ b/tools/decomp-diff.py @@ -11,6 +11,7 @@ python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -s nonmatching python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -d __9CAnimBank + python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -d __9CAnimBank --reloc-diffs all """ import argparse @@ -19,6 +20,7 @@ from typing import Any, Dict, List, Optional, Tuple from _common import ( ROOT_DIR, + RELOC_DIFF_CHOICES, ToolError, build_objdiff_symbol_rows, fail, @@ -29,11 +31,14 @@ OBJDIFF_CLI = os.path.join(root_dir, "build", "tools", "objdiff-cli") -def run_objdiff(unit: str, base_obj: Optional[str] = None) -> Dict[str, Any]: +def run_objdiff( + unit: str, base_obj: Optional[str] = None, reloc_diffs: str = "none" +) -> Dict[str, Any]: return run_objdiff_json( OBJDIFF_CLI, unit, base_obj=base_obj, + reloc_diffs=reloc_diffs, root_dir=root_dir, ) @@ -438,11 +443,22 @@ def main(): "Use this .o file as the decomp base instead of the one from objdiff.json." ), ) + parser.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help=( + "Control relocation-only mismatches in objdiff " + "(default: none; use all to surface relocation diffs)" + ), + ) args = parser.parse_args() try: - data = run_objdiff(args.unit, base_obj=args.base_obj) + data = run_objdiff( + args.unit, base_obj=args.base_obj, reloc_diffs=args.reloc_diffs + ) except ToolError as e: fail(str(e)) diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index f9e9a0fc8..df90eff4a 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -14,6 +14,7 @@ python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --lookup-mode full python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-lookup python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-source + python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zCamera -d UpdateAll --reloc-diffs all python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zCamera """ @@ -29,6 +30,7 @@ from _common import ( BUILD_NINJA, OBJDIFF_JSON, + RELOC_DIFF_CHOICES, ROOT_DIR, ToolError, ensure_exists, @@ -507,6 +509,8 @@ def command_function(args: argparse.Namespace) -> None: cmd.extend(["--ghidra-version", args.ghidra_version]) if args.brief: cmd.append("--brief") + if args.reloc_diffs != "none": + cmd.extend(["--reloc-diffs", args.reloc_diffs]) run_stream(cmd) @@ -526,6 +530,8 @@ def command_unit(args: argparse.Namespace) -> None: ) common_args: List[str] = ["-u", args.unit, "-t", "function"] + if args.reloc_diffs != "none": + common_args.extend(["--reloc-diffs", args.reloc_diffs]) if args.search: common_args.extend(["--search", args.search]) if args.limit is not None: @@ -624,6 +630,8 @@ def command_diff(args: argparse.Namespace) -> None: ensure_shared_unit_output(args.unit) cmd: List[str] = python_tool("decomp-diff.py", "-u", args.unit) + if args.reloc_diffs != "none": + cmd.extend(["--reloc-diffs", args.reloc_diffs]) if args.diff: cmd.extend(["-d", args.diff]) if args.type: @@ -717,6 +725,12 @@ def build_parser() -> argparse.ArgumentParser: action="store_true", help="Trim helper sections like related-source hints and suggested commands", ) + function.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode to decomp-context.py", + ) function.set_defaults(func=command_function) unit = subparsers.add_parser( @@ -730,6 +744,12 @@ def build_parser() -> argparse.ArgumentParser: type=int, help="Limit each symbol list to the first N matching rows", ) + unit.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode to decomp-diff.py", + ) unit.set_defaults(func=command_unit) next_cmd = subparsers.add_parser( @@ -804,6 +824,12 @@ def build_parser() -> argparse.ArgumentParser: action="store_true", help="Don't collapse matching instruction runs", ) + diff.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode to decomp-diff.py", + ) diff.set_defaults(func=command_diff) return parser From 41b71d55b36299e0b2f27847f53b389deaec07db Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 14:34:54 +0100 Subject: [PATCH 007/372] docs: note stub-header ownership cleanup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 1 + AGENTS.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 5aafbce35..d5ec45a39 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -99,6 +99,7 @@ Foo::Foo() - Prefer including the owning repo header over adding a local forward declaration for a project type. - If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. +- If the repo only has an empty or stub owner header, and line info / surrounding source clearly points at that header's subsystem, prefer populating that owner header over leaving a recovered project type declaration inside a `.cpp`. - Only keep a local forward declaration when no canonical repo header exists yet and you have verified that the ownership is still unresolved. - Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. diff --git a/AGENTS.md b/AGENTS.md index 0c400cca5..39b682fe7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -281,6 +281,7 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho - Inline assembly is acceptable when needed to reproduce dead code or compiler scheduling that source alone cannot express cleanly - Preserve the original `class` vs `struct` kind. Check existing headers first, then Dwarf / PS2 info when needed. Even forward declarations and local partial declarations should use the accurate keyword when known. - Prefer including the real repo header over introducing a local forward declaration for a project type. If a type already has a header in `src/`, include it instead of redeclaring it locally. +- If a subsystem already has a stub owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp`. - Preserve original member names, types, order, and proven layout comments. Do not invent `pad`, `unk`, or `field_XXXX` members just to satisfy a guessed size or offset; verify the real members with `find-symbol.py`, GC Dwarf, and PS2 data, and leave a short TODO if a layout detail is still uncertain. - Follow DWARF member naming exactly (`mMember` vs `m_member`) instead of normalizing names - Omit the `this` pointer. From b37777749ad8f153e8763bb4b7ce8a078fad8a21 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 15:20:30 +0100 Subject: [PATCH 008/372] tools: document match-safe null sweeps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 1 + AGENTS.md | 1 + tools/code_style.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index d5ec45a39..950d7c6ee 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -93,6 +93,7 @@ Foo::Foo() - Spell casts without spaces inside the angle brackets: `static_cast(expr)`, not `static_cast< Type * >(expr)`. - Use `nullptr` exclusively for null pointers. - Prefer `if (ptr)` / `if (!ptr)` over explicit null comparisons when the change is local and verified safe. +- When a match-sensitive TU has many explicit `nullptr` checks and you decide to normalize them, prefer one mechanical full-TU pass over piecemeal cleanup. Rebuild the unit and re-check its status before keeping the rewrite. - Inline assembly is acceptable when it is needed to preserve dead-code compares, ordering, or other compiler behavior that source alone cannot reproduce. ### Forward declarations and local prototypes diff --git a/AGENTS.md b/AGENTS.md index 39b682fe7..5a55e5e13 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -286,6 +286,7 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho - Follow DWARF member naming exactly (`mMember` vs `m_member`) instead of normalizing names - Omit the `this` pointer. - Use `nullptr` and `override`. If they are missing, you need to include `types.h`. +- Prefer `if (ptr)` / `if (!ptr)` over explicit `nullptr` comparisons. In match-sensitive translation units, if you choose to normalize many of them, do it as one mechanical TU-wide pass and then rebuild / re-check that unit instead of assuming a piecemeal cleanup is free. - Omit `struct` when declaring variables or parameters, we are not in C land. - Avoid using `using` directives at all cost. Since the game uses jumbo builds, they leak through files. diff --git a/tools/code_style.py b/tools/code_style.py index 1f14f7c72..a1a8f83fa 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -585,7 +585,7 @@ def audit_match_sensitive_cpp( path, idx, "INFO", - "pointer-null comparison is a candidate for `if (ptr)` cleanup, but verify the affected TU first", + "pointer-null comparison is a candidate for `if (ptr)` cleanup; in match-sensitive code, prefer a mechanical full-TU pass and then rebuild/status-check that unit", ) ) return findings From ebaa4a4ca6fe4886ad64d51af0fc7d43ce2c0ede Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 17:37:15 +0100 Subject: [PATCH 009/372] Improve DWARF verification workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/execute/SKILL.md | 14 + .github/skills/implement/SKILL.md | 32 +- .github/skills/refiner/SKILL.md | 20 +- AGENTS.md | 30 ++ tools/decomp-workflow.py | 223 +++++++++++++ tools/dwarf-compare.py | 517 ++++++++++++++++++++++++++++++ tools/lookup.py | 34 +- 7 files changed, 863 insertions(+), 7 deletions(-) create mode 100644 tools/dwarf-compare.py diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index c4366f2a6..b2cf62247 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -9,6 +9,8 @@ Your goal is to decompile a full translation unit: understand the current state, scaffold any missing classes if needed, then match the unit function by function until the produced C++ compiles to byte-identical object code against the original retail binary. +For each function, "done" means both objdiff and normalized DWARF are exact. + ## Overview This workflow combines several smaller workflows: @@ -113,6 +115,7 @@ For each missing or nonmatching function, follow the implementation workflow in - Branch structure mismatches indicate wrong control flow (if/switch/loop) - **Match percentage is misleading.** The last few percent are often the hardest. Treat 95% as unfinished; the goal is 100%. +- **DWARF is equally mandatory.** A 100% objdiff function with a DWARF mismatch is still unfinished. ### 3d. Collect and propagate matching tips @@ -135,6 +138,15 @@ Use `python tools/decomp-workflow.py function ...` or `python tools/decomp-workflow.py diff ...` when you want a shorter, wrapper-first view for one function. +After each function-level edit pass, run: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +If it fails, follow up with `decomp-workflow.py diff` and `decomp-workflow.py dwarf` +until both checks pass. + ### 3g. Periodic reassessment After every few functions, re-run the full status check: @@ -171,6 +183,8 @@ fallback. For any remaining nonmatching functions, make one final pass using the implementation or refiner workflow with all context accumulated during the session. +Do not report a function as complete unless its per-function `verify` check also passes. + ## Phase 5: Report Summarize the session: diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 95ad95016..b51773f02 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -7,6 +7,8 @@ description: Workflow for decompiling and iterating on a function. Your goal is to decompile a specific function: writing C++ source that compiles to byte-identical object code against the original retail binary, verified via `decomp-diff.py`. +A function is not done until it is exact in both objdiff and normalized DWARF. + ## Phase 1: Gather Context Collect data from **all** of these sources in parallel where possible. @@ -145,6 +147,7 @@ For a rebuild plus a standardized diff run, use: ```sh python tools/decomp-workflow.py build -u main/Path/To/TU python tools/decomp-workflow.py diff -u main/Path/To/TU -d FunctionName +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName ``` If the build fails, fix compilation errors first. @@ -172,7 +175,28 @@ Refer to the **Matching Tips** section in AGENTS.md for detailed patterns on resolving instruction mismatches, register allocation issues, stack frame differences, and symbol naming. -After writing your code, occasionally run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing +After each meaningful edit/build iteration, run the combined verification gate first: + +Preferred shortcut: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +This fails unless both the instruction diff and normalized DWARF are exact. + +If the verify gate fails because of DWARF, inspect the DWARF block diff directly: + +```sh +python tools/decomp-workflow.py dwarf -u main/Path/To/TU -f FunctionName +``` + +This gives you a normalized DWARF match percentage plus a diff-like report of what still +differs between the original and rebuilt DWARF blocks for that function. + +Manual fallback: + +After writing your code, you can also run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing due to work on other functions, query the unmangled name instead. ```bash @@ -193,17 +217,19 @@ Repeat the build-diff cycle until the diff shows 100% match with no `~` lines: ```sh python tools/decomp-workflow.py build -u main/Path/To/TU -python tools/decomp-workflow.py diff -u main/Path/To/TU -d FunctionName +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName ``` Every mismatched instruction is a signal — don't settle for "close enough". -Reaching 100% matching status is not enough, also make sure that the dwarf of the function matches the original. +Reaching 100% instruction matching status is not enough. Stay in the loop until `verify` +passes, which means the DWARF of the function also matches after normalization. ## Phase 5: Report Summarize: - Final match status (percentage, instruction count) +- Final DWARF status (exact or remaining mismatch summary) - What the function does (brief description) - Key decisions or tricky patterns used to achieve the match - If not fully matching, document remaining mismatches and theories diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index 82b2a2608..4d1fe1bb7 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -115,7 +115,23 @@ sequences on PPC (see `xoris` pattern in AGENTS.md). Check all casts. ## Phase 3: DWARF verification -After any instruction match, verify the DWARF also matches. +After any instruction match, verify the DWARF also matches. The function is not done +until both objdiff and normalized DWARF are exact. + +Preferred shortcut: + +```bash +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +If the combined gate fails because of DWARF, inspect the DWARF diff directly with: + +```bash +python tools/decomp-workflow.py dwarf -u main/Path/To/TU -f FunctionName +``` + +Manual fallback: + Use the rebuilt shared object from Phase 1 (or rebuild again if you've changed the source): ```bash @@ -143,5 +159,5 @@ Summarize: - What was blocking the match (the root cause category from Phase 1) - The specific source change that resolved it - Any new generalizable assembly pattern discovered (add to AGENTS.md if so) -- DWARF match status +- DWARF match status and whether `verify` passes - If still not matching: the exact diff lines that remain and your best theory diff --git a/AGENTS.md b/AGENTS.md index 5a55e5e13..ff958229f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -156,6 +156,7 @@ python tools/decomp-workflow.py build -u main/Speed/Indep/SourceLists/zAnim python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zAnim -d FindIOWin python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zAnim -f FindIOWin python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zAnim -f FindIOWin --brief +python tools/decomp-workflow.py verify -u main/Speed/Indep/SourceLists/zAnim -f FindIOWin python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zAnim -f FindIOWin --ghidra-version gc python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zAnim -f FindIOWin --lookup-mode full python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zAnim --search FindIOWin --limit 20 @@ -197,6 +198,31 @@ real content. Add `--brief` when you want to keep the helper sections compact; it trims suggested commands and related-source hints without hiding the core status/diff/source data. +For every function you touch, treat DWARF as a first-class completion gate, not a +secondary polish pass. After each meaningful code/build iteration, run the wrapper's +combined verification flow: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +`verify` fails unless **both** checks are exact for that function: + +- objdiff instruction match is 100% +- normalized DWARF block match is exact + +If the combined check fails, then inspect the DWARF diff directly with: + +```sh +python tools/decomp-workflow.py dwarf -u main/Path/To/TU -f FunctionName +``` + +It compares the original and rebuilt DWARF blocks for one function, prints a normalized +DWARF match percentage, and shows a diff-like view of what still differs. Use it +whenever `verify` says the function is still failing the DWARF gate. This is the +fastest way to see whether you are still missing locals, have the wrong inline body, or +changed signature/type details even when the instruction diff already looks good. + When working with these tools, do not just work around recurring friction silently. If you notice a clear, safe workflow or tooling improvement that would make future decomp work faster, shorter, or more reliable, prefer implementing that improvement as part of the task @@ -334,6 +360,10 @@ You may use sub-agents to gather read-only context during this process, but they edit files. Treat their output as analysis input for the main worker, not as a path to delegate source changes. +A function is only done when both objdiff and normalized DWARF are exact. Treat a +100% instruction match with a DWARF mismatch as unfinished work, not a near-complete +result. + The dwarf of your structs doesn't have to neccessarily match the original due to various reasons, just make sure that you copied everything correctly. Never dismiss a diff as "close enough" or "just register allocation." Every mismatched diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index df90eff4a..9193425cd 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -15,6 +15,9 @@ python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-lookup python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-source python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zCamera -d UpdateAll --reloc-diffs all + python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll + python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zAttribSys -f 'Attrib::Class::RemoveCollection(Attrib::Collection *)' --full-diff + python tools/decomp-workflow.py verify -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zCamera """ @@ -33,10 +36,12 @@ RELOC_DIFF_CHOICES, ROOT_DIR, ToolError, + build_objdiff_symbol_rows, ensure_exists, find_objdiff_unit, load_objdiff_config, make_abs, + run_objdiff_json, ) @@ -228,6 +233,77 @@ def describe_path(path: str) -> str: return "present" +def fuzzy_match(pattern: str, name: str) -> bool: + return pattern.lower() in name.lower() + + +def find_objdiff_rows_for_function( + unit_name: str, function_name: str, reloc_diffs: str = "none" +) -> List[Dict[str, Any]]: + data = run_objdiff_json( + OBJDIFF_CLI, + unit_name, + reloc_diffs=reloc_diffs, + root_dir=ROOT_DIR, + ) + rows = [ + row + for row in build_objdiff_symbol_rows(data) + if row["type"] == "function" + ] + + exact_matches = [ + row + for row in rows + if function_name in row["name"] or function_name in row["symbol_name"] + ] + if exact_matches: + return exact_matches + + return [ + row + for row in rows + if fuzzy_match(function_name, row["name"]) + or fuzzy_match(function_name, row["symbol_name"]) + ] + + +def choose_objdiff_row(unit_name: str, function_name: str, reloc_diffs: str = "none") -> Dict[str, Any]: + matches = find_objdiff_rows_for_function(unit_name, function_name, reloc_diffs=reloc_diffs) + if not matches: + raise WorkflowError( + f"objdiff: function '{function_name}' not found in {unit_name}.\n" + "Hint: run `python tools/decomp-workflow.py unit -u " + f"{unit_name} --search {shlex.quote(function_name)}` to inspect nearby symbols." + ) + + if len(matches) > 1: + preview = "\n".join(f" - {row['name']}" for row in matches[:8]) + extra = "" + if len(matches) > 8: + extra = f"\n ... {len(matches) - 8} more" + raise WorkflowError( + f"objdiff: function query '{function_name}' matched multiple symbols in {unit_name}.\n" + f"Use a more specific function name.\n{preview}{extra}" + ) + return matches[0] + + +def load_dwarf_report( + unit_name: str, + function_name: str, + rebuilt_dwarf_file: Optional[str] = None, +) -> Dict[str, Any]: + cmd: List[str] = python_tool("dwarf-compare.py", "-u", unit_name, "-f", function_name, "--json") + if rebuilt_dwarf_file: + cmd.extend(["--rebuilt-dwarf-file", rebuilt_dwarf_file]) + result = run_capture(cmd) + try: + return json.loads(result.stdout) + except json.JSONDecodeError as e: + raise WorkflowError(f"dwarf-compare.py returned invalid JSON: {e}") + + def lookup_symbol_address(symbols_file: str, mangled_name: str) -> Optional[str]: if not os.path.exists(symbols_file): return None @@ -512,6 +588,12 @@ def command_function(args: argparse.Namespace) -> None: if args.reloc_diffs != "none": cmd.extend(["--reloc-diffs", args.reloc_diffs]) run_stream(cmd) + print(flush=True) + print( + "Required completion check: python tools/decomp-workflow.py verify " + f"-u {shlex.quote(args.unit)} -f {shlex.quote(args.function)}", + flush=True, + ) def command_unit(args: argparse.Namespace) -> None: @@ -653,6 +735,88 @@ def command_diff(args: argparse.Namespace) -> None: run_stream(cmd) +def command_dwarf(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + print_section(f"DWARF Workflow: {args.unit} / {args.function}") + if not args.rebuilt_dwarf_file: + ensure_shared_unit_output(args.unit) + + cmd: List[str] = python_tool("dwarf-compare.py", "-u", args.unit, "-f", args.function) + if args.summary: + cmd.append("--summary") + if args.json: + cmd.append("--json") + if args.context is not None: + cmd.extend(["-C", str(args.context)]) + if args.no_collapse: + cmd.append("--no-collapse") + if args.require_exact: + cmd.append("--require-exact") + if args.rebuilt_dwarf_file: + cmd.extend(["--rebuilt-dwarf-file", args.rebuilt_dwarf_file]) + run_stream(cmd) + + +def command_verify(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + print_section(f"Verify Workflow: {args.unit} / {args.function}") + ensure_shared_unit_output(args.unit) + + objdiff_row = choose_objdiff_row(args.unit, args.function, reloc_diffs=args.reloc_diffs) + dwarf_report = load_dwarf_report( + args.unit, + args.function, + rebuilt_dwarf_file=args.rebuilt_dwarf_file, + ) + + objdiff_exact = ( + objdiff_row["status"] == "match" + and objdiff_row["match_percent"] is not None + and float(objdiff_row["match_percent"]) >= 100.0 + ) + dwarf_exact = bool(dwarf_report["normalized_exact_match"]) + overall_ok = objdiff_exact and dwarf_exact + + objdiff_percent = ( + f"{float(objdiff_row['match_percent']):.1f}%" + if objdiff_row["match_percent"] is not None + else "-" + ) + dwarf_percent = f"{float(dwarf_report['match_percent']):.1f}%" + + print( + f"objdiff: {'PASS' if objdiff_exact else 'FAIL'} | " + f"{objdiff_percent} | status={objdiff_row['status']} | " + f"unmatched~{objdiff_row['unmatched_bytes_est']}B" + ) + print( + f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " + f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " + f"change groups={dwarf_report['changed_groups']}" + ) + print(f"Overall: {'PASS' if overall_ok else 'FAIL'}") + print("Done means both objdiff and normalized DWARF are exact for the function.") + + if overall_ok: + return + + print(flush=True) + print("Follow-up commands:", flush=True) + print( + f" python tools/decomp-workflow.py diff -u {shlex.quote(args.unit)} " + f"-d {shlex.quote(args.function)}", + flush=True, + ) + print( + f" python tools/decomp-workflow.py dwarf -u {shlex.quote(args.unit)} " + f"-f {shlex.quote(args.function)}", + flush=True, + ) + raise WorkflowError( + "Verification failed: the function is not complete until both objdiff and DWARF match." + ) + + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description=( @@ -832,6 +996,65 @@ def build_parser() -> argparse.ArgumentParser: ) diff.set_defaults(func=command_diff) + dwarf = subparsers.add_parser( + "dwarf", + help="Compare original vs rebuilt DWARF for one function", + ) + dwarf.add_argument("-u", "--unit", required=True, help="Translation unit name") + dwarf.add_argument("-f", "--function", required=True, help="Function name to compare") + dwarf.add_argument( + "--summary", + action="store_true", + help="Print only the DWARF summary without the diff view", + ) + dwarf.add_argument( + "--json", + action="store_true", + help="Print the DWARF comparison report as JSON", + ) + dwarf.add_argument( + "-C", + "--context", + type=int, + default=3, + help="Context lines around collapsed matching DWARF runs (default: 3)", + ) + dwarf.add_argument( + "--no-collapse", + "--full-diff", + dest="no_collapse", + action="store_true", + help="Show the whole normalized DWARF block with diff markers instead of collapsing matching runs", + ) + dwarf.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + dwarf.add_argument( + "--require-exact", + action="store_true", + help="Exit non-zero unless the normalized DWARF block matches exactly", + ) + dwarf.set_defaults(func=command_dwarf) + + verify = subparsers.add_parser( + "verify", + help="Fail unless one function matches in both objdiff and DWARF", + ) + verify.add_argument("-u", "--unit", required=True, help="Translation unit name") + verify.add_argument("-f", "--function", required=True, help="Function name to verify") + verify.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode when checking instruction match", + ) + verify.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + verify.set_defaults(func=command_verify) + return parser diff --git a/tools/dwarf-compare.py b/tools/dwarf-compare.py new file mode 100644 index 000000000..51f35ea9b --- /dev/null +++ b/tools/dwarf-compare.py @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 + +""" +Compare the original DWARF for one function against the rebuilt DWARF from a unit object. + +Examples: + python tools/dwarf-compare.py -u main/Speed/Indep/SourceLists/zCamera -f "Camera::UpdateAll(float)" + python tools/dwarf-compare.py -u main/Speed/Indep/SourceLists/zAI -f "AIPursuit::AIPursuit(Sim::Param)" --summary + python tools/dwarf-compare.py -u main/Speed/Indep/SourceLists/zAttribSys -f "Attrib::Class::RemoveCollection(Attrib::Collection *)" --full-diff + python tools/dwarf-compare.py -u main/Speed/Indep/SourceLists/zCamera -f "Camera::UpdateAll(float)" --require-exact +""" + +import argparse +import difflib +import json +import os +import re +import sys +from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple + +from _common import ROOT_DIR, ToolError, find_objdiff_unit, load_objdiff_config, make_abs +from lookup import ( + _candidate_func_names, + _normalise_func_name, + _sig_contains_name, + read_text, + split_functions, +) + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +TOOLS_DIR = os.path.join(ROOT_DIR, "tools") +GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") +DTK = os.path.join(ROOT_DIR, "build", "tools", "dtk") +HEX_RE = re.compile(r"0x[0-9A-Fa-f]+") + + +class DwarfCompareError(RuntimeError): + pass + + +FunctionBlock = Tuple[str, str, str, str] + + +def tool_path(name: str) -> str: + return os.path.join(TOOLS_DIR, name) + + +def print_section(title: str) -> None: + print(flush=True) + print("=" * 60, flush=True) + print(f" {title}", flush=True) + print("=" * 60, flush=True) + + +def format_failure( + cmd: Sequence[str], returncode: int, stdout: str = "", stderr: str = "" +) -> str: + message = [f"Command failed (exit {returncode}): {' '.join(cmd)}"] + stdout = stdout.strip() + stderr = stderr.strip() + if stdout: + message.append(f"stdout:\n{stdout}") + if stderr: + message.append(f"stderr:\n{stderr}") + return "\n".join(message) + + +def maybe_remove(path: Optional[str]) -> None: + if not path: + return + try: + if os.path.exists(path): + os.remove(path) + except OSError as e: + print(f"Warning: failed to remove temporary file {path}: {e}", file=sys.stderr) + + +def get_unit_build_output(unit_name: str) -> str: + config = load_objdiff_config() + unit = find_objdiff_unit(config, unit_name) + if unit is None: + raise DwarfCompareError(f"Unit not found in objdiff.json: {unit_name}") + + target = unit.get("base_path") or unit.get("target_path") + if not target: + raise DwarfCompareError(f"Unit has no build target in objdiff.json: {unit_name}") + return make_abs(str(target)) or str(target) + + +def dtk_dwarf_dump(obj_path: str) -> str: + import tempfile + import subprocess + + fd, output_path = tempfile.mkstemp(prefix="nfsmw_dwarf_compare_", suffix=".nothpp") + os.close(fd) + maybe_remove(output_path) + + result = subprocess.run( + [DTK, "dwarf", "dump", obj_path, "-o", output_path], + cwd=ROOT_DIR, + text=True, + capture_output=True, + ) + if result.returncode != 0: + maybe_remove(output_path) + raise DwarfCompareError( + format_failure( + [DTK, "dwarf", "dump", obj_path, "-o", output_path], + result.returncode, + result.stdout, + result.stderr, + ) + ) + + tool_output = "\n".join( + part.strip() for part in [result.stdout, result.stderr] if part.strip() + ) + if "ERROR " in tool_output or tool_output.startswith("ERROR"): + maybe_remove(output_path) + raise DwarfCompareError( + f"dtk reported an error while dumping DWARF:\n{tool_output}" + ) + + if not os.path.exists(output_path): + raise DwarfCompareError("dtk dwarf dump succeeded but did not write an output file") + + return output_path + + +def load_function_blocks(path: str, folder_mode: bool) -> List[FunctionBlock]: + if folder_mode: + text = read_text(os.path.join(path, "functions.nothpp")) + else: + text = read_text(path) + return split_functions(text) + + +def find_function_blocks(funcs: Iterable[FunctionBlock], query: str) -> List[FunctionBlock]: + candidates = _candidate_func_names(query) + matches: List[FunctionBlock] = [] + exact_substring_matches: List[FunctionBlock] = [] + + for func in funcs: + sig_line = func[2] + if query in sig_line: + exact_substring_matches.append(func) + if any(_sig_contains_name(sig_line, candidate) for candidate in candidates): + matches.append(func) + + if exact_substring_matches: + return exact_substring_matches + return matches + + +def last_name_token(query: str) -> str: + bare = _normalise_func_name(query) + if "::" in bare: + return bare.split("::")[-1] + return bare + + +def find_similar_signatures( + funcs: Sequence[FunctionBlock], query: str, limit: int = 8 +) -> List[str]: + token = last_name_token(query) + token_matches: List[str] = [] + seen = set() + + for _, _, sig_line, _ in funcs: + if token and token in sig_line and sig_line not in seen: + token_matches.append(sig_line) + seen.add(sig_line) + if len(token_matches) >= limit: + return token_matches + + choices = [sig_line for _, _, sig_line, _ in funcs if sig_line] + for sig_line in difflib.get_close_matches(query, choices, n=limit, cutoff=0.35): + if sig_line not in seen: + token_matches.append(sig_line) + seen.add(sig_line) + if len(token_matches) >= limit: + break + return token_matches + + +def choose_function_block( + funcs: List[FunctionBlock], query: str, label: str +) -> FunctionBlock: + matches = find_function_blocks(funcs, query) + if not matches: + if not funcs: + raise DwarfCompareError( + f"{label}: function '{query}' not found.\n" + "The scanned DWARF source contains no top-level function blocks." + ) + + similar = find_similar_signatures(funcs, query) + details = [ + f"{label}: function '{query}' not found.", + f"Scanned {len(funcs)} top-level function block(s).", + ] + if similar: + details.append("Closest signatures:") + details.extend(f" - {sig}" for sig in similar) + raise DwarfCompareError("\n".join(details)) + if len(matches) > 1: + signatures = "\n".join(f" - {match[2]}" for match in matches[:8]) + extra = "" + if len(matches) > 8: + extra = f"\n ... {len(matches) - 8} more" + raise DwarfCompareError( + f"{label}: function query '{query}' matched multiple DWARF blocks.\n" + f"Use a more specific function name.\n{signatures}{extra}" + ) + return matches[0] + + +def normalize_line(line: str) -> str: + stripped = line.rstrip("\n").rstrip() + if stripped.startswith("// Range:"): + return "// Range: " + return HEX_RE.sub("0xADDR", stripped) + + +def normalize_block(block: str) -> List[str]: + return [normalize_line(line) for line in block.splitlines()] + + +def count_lines_for_opcodes(opcodes: Sequence[Tuple[str, int, int, int, int]]) -> Dict[str, int]: + matching = 0 + original_only = 0 + rebuilt_only = 0 + changed_groups = 0 + for tag, i1, i2, j1, j2 in opcodes: + if tag == "equal": + matching += i2 - i1 + continue + changed_groups += 1 + if tag in ("replace", "delete"): + original_only += i2 - i1 + if tag in ("replace", "insert"): + rebuilt_only += j2 - j1 + return { + "matching_lines": matching, + "original_only_lines": original_only, + "rebuilt_only_lines": rebuilt_only, + "changed_groups": changed_groups, + } + + +def build_diff_lines( + original_lines: Sequence[str], + rebuilt_lines: Sequence[str], + function_name: str, + context: int, + collapse: bool, +) -> List[str]: + if list(original_lines) == list(rebuilt_lines): + return [] + + rendered: List[str] = [ + f"--- original:{function_name}", + f"+++ rebuilt:{function_name}", + ] + + matcher = difflib.SequenceMatcher(a=original_lines, b=rebuilt_lines) + for group in matcher.get_grouped_opcodes(context if collapse else max(len(original_lines), len(rebuilt_lines))): + first = group[0] + last = group[-1] + a_start = first[1] + 1 + a_len = last[2] - first[1] + b_start = first[3] + 1 + b_len = last[4] - first[3] + rendered.append(f"@@ -{a_start},{a_len} +{b_start},{b_len} @@") + + for tag, i1, i2, j1, j2 in group: + if tag == "equal": + for idx in range(i1, i2): + rendered.append(f" L{idx + 1:04d} {original_lines[idx]}") + continue + + if tag in ("replace", "delete"): + for idx in range(i1, i2): + rendered.append(f"- L{idx + 1:04d} {original_lines[idx]}") + if tag in ("replace", "insert"): + for idx in range(j1, j2): + rendered.append(f"+ L{idx + 1:04d} {rebuilt_lines[idx]}") + + return rendered + + +def build_report( + unit_name: str, + function_name: str, + original_block: FunctionBlock, + rebuilt_block: FunctionBlock, + collapse: bool, + context: int, +) -> Dict[str, Any]: + original_raw = original_block[3].splitlines() + rebuilt_raw = rebuilt_block[3].splitlines() + original_lines = normalize_block(original_block[3]) + rebuilt_lines = normalize_block(rebuilt_block[3]) + + matcher = difflib.SequenceMatcher(a=original_lines, b=rebuilt_lines) + opcodes = matcher.get_opcodes() + counts = count_lines_for_opcodes(opcodes) + total_lines = max(len(original_lines), len(rebuilt_lines), 1) + match_percent = 100.0 * counts["matching_lines"] / total_lines + signature_match = normalize_line(original_block[2]) == normalize_line(rebuilt_block[2]) + raw_exact_match = original_raw == rebuilt_raw + normalized_exact_match = original_lines == rebuilt_lines + + diff_lines = build_diff_lines( + original_lines, + rebuilt_lines, + function_name, + context=context, + collapse=collapse, + ) + mismatch_summaries: List[str] = [] + for tag, i1, i2, j1, j2 in opcodes: + if tag == "equal": + continue + original_span = ( + f"L{i1 + 1:04d}" if i2 - i1 <= 1 else f"L{i1 + 1:04d}-L{i2:04d}" + ) if tag in ("replace", "delete") else "-" + rebuilt_span = ( + f"L{j1 + 1:04d}" if j2 - j1 <= 1 else f"L{j1 + 1:04d}-L{j2:04d}" + ) if tag in ("replace", "insert") else "-" + + if tag == "replace" and i2 - i1 == 1 and j2 - j1 == 1: + detail = f"{original_lines[i1]} -> {rebuilt_lines[j1]}" + elif tag == "delete": + detail = f"removed {i2 - i1} original line(s)" + elif tag == "insert": + detail = f"added {j2 - j1} rebuilt line(s)" + else: + detail = ( + f"replaced {i2 - i1} original line(s) with " + f"{j2 - j1} rebuilt line(s)" + ) + mismatch_summaries.append( + f"- {original_span} -> {rebuilt_span}: {detail}" + ) + + return { + "unit": unit_name, + "function": function_name, + "match_percent": match_percent, + "matching_lines": counts["matching_lines"], + "total_lines": total_lines, + "original_line_count": len(original_lines), + "rebuilt_line_count": len(rebuilt_lines), + "original_only_lines": counts["original_only_lines"], + "rebuilt_only_lines": counts["rebuilt_only_lines"], + "changed_groups": counts["changed_groups"], + "signature_match": signature_match, + "normalized_exact_match": normalized_exact_match, + "raw_exact_match": raw_exact_match, + "original_signature": original_block[2], + "rebuilt_signature": rebuilt_block[2], + "original_range": [original_block[0], original_block[1]], + "rebuilt_range": [rebuilt_block[0], rebuilt_block[1]], + "mismatch_summaries": mismatch_summaries, + "diff_lines": diff_lines, + } + + +def print_summary(report: Dict[str, Any]) -> None: + print_section(f"DWARF Match: {report['function']}") + print(f"Unit: {report['unit']}") + print( + f"Normalized DWARF match: {report['match_percent']:.1f}% " + f"({report['matching_lines']}/{report['total_lines']} lines)" + ) + print( + f"Signature: {'match' if report['signature_match'] else 'mismatch'} | " + f"Change groups: {report['changed_groups']} | " + f"Original-only lines: {report['original_only_lines']} | " + f"Rebuilt-only lines: {report['rebuilt_only_lines']}" + ) + print( + f"Normalized exact match: {'yes' if report['normalized_exact_match'] else 'no'}" + ) + if report["normalized_exact_match"] and not report["raw_exact_match"]: + print("Raw textual exact match: no (only raw addresses/ranges differ)") + else: + print(f"Raw textual exact match: {'yes' if report['raw_exact_match'] else 'no'}") + print( + "Address-only range differences are normalized out so the percentage tracks " + "structural/function-body DWARF changes." + ) + if not report["signature_match"]: + print() + print("Original signature:") + print(f" {report['original_signature']}") + print("Rebuilt signature:") + print(f" {report['rebuilt_signature']}") + + +def print_diff(report: Dict[str, Any]) -> None: + if report["mismatch_summaries"]: + print_section("Mismatch Summary") + for line in report["mismatch_summaries"]: + print(line) + print_section("DWARF Diff") + if not report["diff_lines"]: + print("No DWARF differences after normalization.") + return + for line in report["diff_lines"]: + print(line) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Compare original and rebuilt DWARF for one function and show a " + "normalized line-match report plus a diff-like view." + ) + ) + parser.add_argument("-u", "--unit", required=True, help="Translation unit name") + parser.add_argument("-f", "--function", required=True, help="Function name to compare") + parser.add_argument( + "--summary", + action="store_true", + help="Print only the summary header without the diff view", + ) + parser.add_argument( + "--json", + action="store_true", + help="Print the report as JSON", + ) + parser.add_argument( + "-C", + "--context", + type=int, + default=3, + help="Context lines to keep around collapsed matching runs (default: 3)", + ) + parser.add_argument( + "--no-collapse", + "--full-diff", + dest="no_collapse", + action="store_true", + help="Show the whole normalized DWARF block with diff markers instead of collapsing matching runs", + ) + parser.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + parser.add_argument( + "--require-exact", + action="store_true", + help="Exit non-zero unless the normalized DWARF block matches exactly", + ) + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + + rebuilt_dwarf_path: Optional[str] = None + cleanup_rebuilt_dwarf = False + try: + if args.rebuilt_dwarf_file: + rebuilt_dwarf_path = os.path.abspath(args.rebuilt_dwarf_file) + else: + obj_path = get_unit_build_output(args.unit) + if not os.path.exists(obj_path): + raise DwarfCompareError( + f"Missing built object for {args.unit}: {obj_path}\n" + f"Hint: run `python tools/decomp-workflow.py build -u {args.unit}` " + "or use the wrapper `python tools/decomp-workflow.py dwarf ...`." + ) + rebuilt_dwarf_path = dtk_dwarf_dump(obj_path) + cleanup_rebuilt_dwarf = True + + original_funcs = load_function_blocks(GC_DWARF, folder_mode=True) + rebuilt_funcs = load_function_blocks(rebuilt_dwarf_path, folder_mode=False) + + original_block = choose_function_block(original_funcs, args.function, "original DWARF") + rebuilt_block = choose_function_block(rebuilt_funcs, args.function, "rebuilt DWARF") + + report = build_report( + args.unit, + args.function, + original_block, + rebuilt_block, + collapse=not args.no_collapse, + context=args.context, + ) + + if args.json: + print(json.dumps(report, indent=2)) + if args.require_exact and not report["normalized_exact_match"]: + sys.exit(1) + return + + print_summary(report) + if not args.summary: + print_diff(report) + if args.require_exact and not report["normalized_exact_match"]: + sys.exit(1) + + except (DwarfCompareError, ToolError) as e: + print(e, file=sys.stderr) + sys.exit(1) + finally: + if cleanup_rebuilt_dwarf: + maybe_remove(rebuilt_dwarf_path) + + +if __name__ == "__main__": + main() diff --git a/tools/lookup.py b/tools/lookup.py index e8188bfca..cecbae991 100644 --- a/tools/lookup.py +++ b/tools/lookup.py @@ -248,6 +248,32 @@ def _normalise_func_name(name: str) -> str: return name.strip() +def _candidate_func_names(name: str) -> list[str]: + """ + Generate progressively shorter qualified-name suffixes. + + Example: + Attrib::Class::RemoveCollection -> [ + 'Attrib::Class::RemoveCollection', + 'Class::RemoveCollection', + 'RemoveCollection', + ] + + This helps match DWARF signatures that omit leading namespaces. + """ + bare = _normalise_func_name(name) + if not bare: + return [] + + parts = bare.split("::") + candidates: list[str] = [] + for index in range(len(parts)): + candidate = "::".join(parts[index:]).strip() + if candidate and candidate not in candidates: + candidates.append(candidate) + return candidates + + def _sig_contains_name(sig_line: str, bare_name: str) -> bool: """ Return True if *bare_name* (e.g. 'EPerfectLaunch::~EPerfectLaunch') appears @@ -291,8 +317,12 @@ def find_functions_by_name( funcs: list[tuple[str, str, str, str]], query: str ) -> list[str]: """Return all function blocks whose signature matches *query* (ignoring params).""" - bare = _normalise_func_name(query) - return [block for start, end, sig, block in funcs if _sig_contains_name(sig, bare)] + candidates = _candidate_func_names(query) + return [ + block + for start, end, sig, block in funcs + if any(_sig_contains_name(sig, candidate) for candidate in candidates) + ] # --------------------------------------------------------------------------- From cc4c6904c9e179b74ef1039e08f579d698a5db2a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 17:38:03 +0100 Subject: [PATCH 010/372] - again --- tools/build-unit.py | 324 -------------------------------------------- 1 file changed, 324 deletions(-) delete mode 100644 tools/build-unit.py diff --git a/tools/build-unit.py b/tools/build-unit.py deleted file mode 100644 index dbfacf7b7..000000000 --- a/tools/build-unit.py +++ /dev/null @@ -1,324 +0,0 @@ -#!/usr/bin/env python3 - -""" -Compile a single translation unit to a temporary (or specified) object file. - -Uses `ninja -t compdb` to extract the exact compile command for the unit, then -redirects the output to a private path so parallel agents never overwrite each -other's work. - -Usage: - # Auto-generate a temp path (printed to stdout): - python tools/build-unit.py -u main/Speed/Indep/SourceLists/zAnim - - # Compile to an explicit path: - python tools/build-unit.py -u main/Speed/Indep/SourceLists/zAnim -o /tmp/my.o - -The path of the compiled .o is always printed to stdout on success so it can be -captured with command substitution: - - TEMPOBJ=$(python tools/build-unit.py -u main/Speed/Indep/SourceLists/zAnim) - python tools/decomp-diff.py -u main/Speed/Indep/SourceLists/zAnim -d MyFunc --base-obj "$TEMPOBJ" - dtk dwarf dump "$TEMPOBJ" -o /tmp/my_dwarf.nothpp -""" - -import argparse -import json -import os -import re -import subprocess -import sys -import tempfile -from typing import Any, Dict, List, Optional, Tuple, Union - -script_dir = os.path.dirname(os.path.realpath(__file__)) -root_dir = os.path.abspath(os.path.join(script_dir, "..")) -OBJDIFF_JSON = os.path.join(root_dir, "objdiff.json") -BUILD_NINJA = os.path.join(root_dir, "build.ninja") -COMPILE_COMMANDS = os.path.join(root_dir, "compile_commands.json") - -Command = Union[str, List[str]] - - -def load_objdiff() -> Dict[str, Any]: - with open(OBJDIFF_JSON) as f: - return json.load(f) - - -def find_unit_source(config: Dict[str, Any], unit_name: str) -> Optional[str]: - """Return the source_path for a unit from objdiff.json, or None.""" - for unit in config.get("units", []): - if unit["name"] == unit_name: - src = unit.get("metadata", {}).get("source_path") - return str(src) if src else None - return None - - -def find_unit_target(config: Dict[str, Any], unit_name: str) -> Optional[str]: - """Return the build target path for a unit from objdiff.json, or None.""" - for unit in config.get("units", []): - if unit["name"] == unit_name: - target = unit.get("base_path") or unit.get("target_path") - return str(target) if target else None - return None - - -def get_compdb() -> Optional[List[Dict[str, Any]]]: - """Load compile_commands.json, falling back to `ninja -t compdb` if needed.""" - if os.path.exists(COMPILE_COMMANDS): - try: - with open(COMPILE_COMMANDS) as f: - return json.load(f) - except json.JSONDecodeError as e: - print(f"Failed to parse compile_commands.json: {e}", file=sys.stderr) - - result = subprocess.run( - ["ninja", "-t", "compdb"], - capture_output=True, - cwd=root_dir, - ) - if result.returncode != 0: - print( - f"ninja -t compdb failed:\n{result.stderr.decode(errors='replace')}", - file=sys.stderr, - ) - return None - try: - return json.loads(result.stdout) - except json.JSONDecodeError as e: - print(f"Failed to parse ninja compdb output: {e}", file=sys.stderr) - return None - - -def get_build_command(target_path: str) -> Optional[str]: - """Return the final ninja command used to build target_path.""" - result = subprocess.run( - ["ninja", "-t", "commands", target_path], - capture_output=True, - cwd=root_dir, - ) - if result.returncode != 0: - print( - f"ninja -t commands failed:\n{result.stderr.decode(errors='replace')}", - file=sys.stderr, - ) - return None - - commands = [line.strip() for line in result.stdout.decode().splitlines() if line.strip()] - return commands[-1] if commands else None - - -def find_entry( - compdb: List[Dict[str, Any]], source_path: str -) -> Optional[Dict[str, Any]]: - """Find the compdb entry whose 'file' matches source_path. - - Prefers entries whose output is a .o file (actual compiler invocations) - over auxiliary entries (e.g. hash generation). - """ - abs_source = os.path.normcase(os.path.abspath(os.path.join(root_dir, source_path))) - candidates = [] - for entry in compdb: - file_val = entry.get("file", "") - if not os.path.isabs(file_val): - entry_dir = entry.get("directory", root_dir) - file_val = os.path.abspath(os.path.join(entry_dir, file_val)) - if os.path.normcase(file_val) == abs_source: - candidates.append(entry) - for entry in candidates: - out = entry.get("output", "") - if out.endswith(".o") or out.endswith(".obj"): - return entry - return candidates[0] if candidates else None - - -def get_command(entry: Dict[str, Any]) -> Command: - command = entry.get("command") - if isinstance(command, str): - return command - - arguments = entry.get("arguments") - if isinstance(arguments, list): - return arguments[:] - - print( - "Compilation entry is missing both 'command' and 'arguments'", - file=sys.stderr, - ) - sys.exit(1) - - -def strip_transform_dep(command: Command) -> Command: - """Remove the `&& python transform_dep.py ...` step from a compile command. - - The dependency file transformation is only needed for incremental ninja - builds; it is safe to skip for one-off temp compilations. - """ - if isinstance(command, list): - return command - return re.sub( - r"\s*&&\s*\S*python3?\S*\s+\S*transform_dep\.py\s+\S+\s+\S+", - "", - command, - ) - - -def find_output_argument(command: Command) -> Optional[Tuple[int, str]]: - if isinstance(command, list): - for i in range(len(command) - 1): - if command[i] == "-o": - return i + 1, command[i + 1] - return None - - m = re.search(r"(? Command: - """Replace the compiler output path in command with new_output. - - Handles two styles: - - Direct file output (-o path/to/file.o): ngccc/ProDG, MSVC, EE-GCC - - Directory output (-o path/to/dir): mwcc (MWCC outputs to a dir) - """ - output_arg = find_output_argument(command) - if output_arg is None: - print("Could not find -o argument in compile command", file=sys.stderr) - sys.exit(1) - - index, o_arg = output_arg - - if o_arg.endswith(".o") or o_arg.endswith(".obj"): - replacement = new_output - else: - replacement = os.path.dirname(new_output) - - if isinstance(command, list): - new_command = command[:] - new_command[index] = replacement - return new_command - - return command[:index] + replacement + command[index + len(o_arg) :] - - -def actual_output_path(command: Command, source_path: str, new_output: str) -> str: - """Return the path where the compiled .o actually lands. - - For direct-file compilers this is new_output. For directory-output - compilers it is /.o. - """ - output_arg = find_output_argument(command) - if output_arg is None: - return new_output - _, o_arg = output_arg - if o_arg.endswith(".o") or o_arg.endswith(".obj"): - return new_output - stem = os.path.splitext(os.path.basename(source_path))[0] - return os.path.join(os.path.dirname(new_output), stem + ".o") - - -def compile_unit(unit_name: str, output_path: str) -> str: - """Compile unit to output_path and return the actual .o path.""" - if not os.path.exists(OBJDIFF_JSON): - print( - "objdiff.json not found. Run: python configure.py && ninja all_source", - file=sys.stderr, - ) - sys.exit(1) - - config = load_objdiff() - source_path = find_unit_source(config, unit_name) - target_path = find_unit_target(config, unit_name) - if not source_path: - print( - f"No source_path found for unit '{unit_name}' in objdiff.json.\n" - "The unit may not have a source file yet (missing implementation).", - file=sys.stderr, - ) - sys.exit(1) - if not target_path: - print( - f"No target_path found for unit '{unit_name}' in objdiff.json.", - file=sys.stderr, - ) - sys.exit(1) - - if not os.path.exists(BUILD_NINJA): - print( - "build.ninja not found. Run: python configure.py && ninja all_source", - file=sys.stderr, - ) - sys.exit(1) - - command = get_build_command(target_path) - if command is None: - print( - f"No build command found for target '{target_path}'.\n" - "Make sure the unit exists and `python configure.py` has been run.", - file=sys.stderr, - ) - sys.exit(1) - - # 1. Strip the dependency-file transform step — not needed for temp builds. - command = strip_transform_dep(command) - - # 2. Determine the actual output path before modifying the command. - actual = actual_output_path(command, source_path, output_path) - - # 3. Ensure the output directory exists. - out_dir = os.path.dirname(actual) - if out_dir: - os.makedirs(out_dir, exist_ok=True) - - # 4. Redirect the compiler output. - command = redirect_output(command, source_path, output_path) - - # 5. Run the compile. - result = subprocess.run(command, shell=isinstance(command, str), cwd=root_dir) - if result.returncode != 0: - print( - f"Compilation failed (exit code {result.returncode})", file=sys.stderr - ) - sys.exit(1) - - return actual - - -def main() -> None: - parser = argparse.ArgumentParser( - description=( - "Compile a translation unit to a temporary or specified .o file. " - "Safe to run in parallel — each call produces an independent output." - ) - ) - parser.add_argument( - "-u", - "--unit", - required=True, - help="Unit name (e.g. main/Speed/Indep/SourceLists/zAnim)", - ) - parser.add_argument( - "-o", - "--output", - help="Explicit output .o path (default: auto-generated temp file)", - ) - args = parser.parse_args() - - if args.output: - output_path = os.path.abspath(args.output) - else: - unit_stem = os.path.basename(args.unit) - fd, output_path = tempfile.mkstemp( - prefix=f"nfsmw_{unit_stem}_", - suffix=".o", - ) - os.close(fd) - - actual = compile_unit(args.unit, output_path) - print(actual) - - -if __name__ == "__main__": - main() From 718ab67aa4093f67f585863ac978f88b12350329 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 17:44:32 +0100 Subject: [PATCH 011/372] Add merge rollout helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/merge-main-rollout.py | 327 ++++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 tools/merge-main-rollout.py diff --git a/tools/merge-main-rollout.py b/tools/merge-main-rollout.py new file mode 100644 index 000000000..4848fbdfd --- /dev/null +++ b/tools/merge-main-rollout.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 + +""" +Safely merge a base branch into recent local branches and open-PR branches. + +Examples: + python tools/merge-main-rollout.py --base main --recent-hours 5 --pr-repo dbalatoni13/nfsmw --dry-run + python tools/merge-main-rollout.py --base main --recent-hours 5 --pr-repo dbalatoni13/nfsmw +""" + +import argparse +import json +import os +import shutil +import subprocess +import sys +import tempfile +import time +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Sequence, Set, Tuple + +from _common import ROOT_DIR, format_subprocess_error + + +TRAILER = "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + + +class RolloutError(RuntimeError): + pass + + +@dataclass +class TargetBranch: + name: str + reasons: Set[str] = field(default_factory=set) + + +@dataclass +class MergeResult: + branch: str + status: str + detail: str + + +def run_capture(cmd: Sequence[str], cwd: str = ROOT_DIR) -> subprocess.CompletedProcess[str]: + result = subprocess.run( + cmd, + cwd=cwd, + text=True, + capture_output=True, + ) + if result.returncode != 0: + raise RolloutError(format_subprocess_error(cmd, result.returncode, result.stdout, result.stderr)) + return result + + +def run_stream(cmd: Sequence[str], cwd: str = ROOT_DIR) -> None: + result = subprocess.run(cmd, cwd=cwd, text=True) + if result.returncode != 0: + raise RolloutError(format_subprocess_error(cmd, result.returncode)) + + +def git_common_dir() -> str: + return run_capture(["git", "rev-parse", "--git-common-dir"]).stdout.strip() + + +def get_worktrees() -> Dict[str, str]: + result = run_capture(["git", "worktree", "list", "--porcelain"]) + worktrees: Dict[str, str] = {} + current: Dict[str, str] = {} + for line in result.stdout.splitlines(): + if not line: + branch = current.get("branch", "") + worktree = current.get("worktree") + if branch.startswith("refs/heads/") and worktree: + worktrees[branch.replace("refs/heads/", "", 1)] = worktree + current = {} + continue + key, _, value = line.partition(" ") + current[key] = value + if current: + branch = current.get("branch", "") + worktree = current.get("worktree") + if branch.startswith("refs/heads/") and worktree: + worktrees[branch.replace("refs/heads/", "", 1)] = worktree + return worktrees + + +def worktree_dirty(path: str) -> bool: + result = run_capture(["git", "status", "--short"], cwd=path) + return bool(result.stdout.strip()) + + +def get_recent_local_branches(hours: float) -> List[str]: + threshold = int(time.time() - hours * 3600.0) + result = run_capture( + [ + "git", + "for-each-ref", + "--format=%(refname:short)\t%(committerdate:unix)", + "refs/heads", + ] + ) + recent: List[str] = [] + for line in result.stdout.splitlines(): + if not line.strip(): + continue + name, _, ts = line.partition("\t") + if not name or not ts: + continue + if int(ts) >= threshold: + recent.append(name) + return recent + + +def get_open_pr_branches(repo: str) -> List[Tuple[str, str]]: + result = run_capture( + [ + "gh", + "pr", + "list", + "--repo", + repo, + "--state", + "open", + "--limit", + "100", + "--json", + "headRefName,headRepositoryOwner", + ] + ) + data = json.loads(result.stdout) + refs: List[Tuple[str, str]] = [] + for entry in data: + branch = entry.get("headRefName") + owner = ((entry.get("headRepositoryOwner") or {}).get("login") or "").strip() + if branch: + refs.append((branch, owner)) + return refs + + +def local_branch_exists(branch: str) -> bool: + result = subprocess.run( + ["git", "show-ref", "--verify", "--quiet", f"refs/heads/{branch}"], + cwd=ROOT_DIR, + text=True, + ) + return result.returncode == 0 + + +def discover_targets(base: str, recent_hours: float, pr_repos: Sequence[str]) -> Dict[str, TargetBranch]: + targets: Dict[str, TargetBranch] = {} + for branch in get_recent_local_branches(recent_hours): + if branch == base: + continue + target = targets.setdefault(branch, TargetBranch(branch)) + target.reasons.add(f"recent<={recent_hours:g}h") + + for repo in pr_repos: + for branch, owner in get_open_pr_branches(repo): + if branch == base: + continue + target = targets.setdefault(branch, TargetBranch(branch)) + if owner: + target.reasons.add(f"open-pr:{repo}:{owner}") + else: + target.reasons.add(f"open-pr:{repo}") + return targets + + +def ensure_temp_worktree(branch: str, temp_root: str) -> str: + os.makedirs(temp_root, exist_ok=True) + path = tempfile.mkdtemp(prefix=f"{branch.replace('/', '_')}_", dir=temp_root) + try: + run_capture(["git", "worktree", "add", path, branch]) + except Exception: + shutil.rmtree(path, ignore_errors=True) + raise + return path + + +def remove_temp_worktree(path: str) -> None: + result = subprocess.run( + ["git", "worktree", "remove", "--force", path], + cwd=ROOT_DIR, + text=True, + capture_output=True, + ) + if result.returncode != 0: + shutil.rmtree(path, ignore_errors=True) + + +def merge_into_branch(branch: str, base: str, path: str) -> MergeResult: + before = run_capture(["git", "rev-parse", "HEAD"], cwd=path).stdout.strip() + merge_message = f"Merge {base} into {branch}\n\n{TRAILER}" + result = subprocess.run( + ["git", "merge", "--no-ff", base, "-m", merge_message], + cwd=path, + text=True, + capture_output=True, + ) + if result.returncode != 0: + subprocess.run(["git", "merge", "--abort"], cwd=path, text=True, capture_output=True) + return MergeResult( + branch=branch, + status="conflict", + detail=(result.stderr.strip() or result.stdout.strip() or "merge failed"), + ) + + after = run_capture(["git", "rev-parse", "HEAD"], cwd=path).stdout.strip() + if before == after: + return MergeResult(branch=branch, status="up-to-date", detail="already contained base") + return MergeResult(branch=branch, status="merged", detail=after) + + +def rollout( + base: str, + recent_hours: float, + pr_repos: Sequence[str], + dry_run: bool, +) -> List[MergeResult]: + targets = discover_targets(base, recent_hours, pr_repos) + worktrees = get_worktrees() + temp_root = os.path.join(git_common_dir(), "merge-rollout-worktrees") + results: List[MergeResult] = [] + + for branch in sorted(targets): + if branch == base: + continue + + target = targets[branch] + reason_text = ", ".join(sorted(target.reasons)) + + if not local_branch_exists(branch): + results.append( + MergeResult(branch=branch, status="skipped", detail=f"no local branch ({reason_text})") + ) + continue + + existing_path = worktrees.get(branch) + temp_path: Optional[str] = None + path = existing_path + try: + if existing_path: + if worktree_dirty(existing_path): + results.append( + MergeResult( + branch=branch, + status="skipped", + detail=f"dirty worktree: {existing_path} ({reason_text})", + ) + ) + continue + else: + temp_path = ensure_temp_worktree(branch, temp_root) + path = temp_path + + assert path is not None + if dry_run: + location = existing_path if existing_path else temp_path + results.append( + MergeResult( + branch=branch, + status="would-merge", + detail=f"{location} ({reason_text})", + ) + ) + continue + + results.append(merge_into_branch(branch, base, path)) + finally: + if temp_path is not None: + remove_temp_worktree(temp_path) + + return results + + +def print_results(results: Sequence[MergeResult]) -> None: + print(f"{'BRANCH':<40} {'STATUS':<12} DETAIL") + print("-" * 120) + for result in results: + print(f"{result.branch:<40} {result.status:<12} {result.detail}") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Safely merge a base branch into recent local branches and local open-PR branches.", + ) + parser.add_argument("--base", default="main", help="Base branch to merge from (default: main)") + parser.add_argument( + "--recent-hours", + type=float, + default=5.0, + help="Include local branches with commits in the last N hours (default: 5)", + ) + parser.add_argument( + "--pr-repo", + action="append", + default=[], + help="GitHub repo in OWNER/REPO form; include open PR head branches if they exist locally", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Discover and print targets without merging", + ) + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + try: + results = rollout( + base=args.base, + recent_hours=args.recent_hours, + pr_repos=args.pr_repo, + dry_run=args.dry_run, + ) + print_results(results) + except RolloutError as e: + print(e, file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() From 9ff950b49f1c28851c4fe21bed3e4242ba108bb6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 17:49:22 +0100 Subject: [PATCH 012/372] Support dirty merge rollout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/merge-main-rollout.py | 79 +++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/tools/merge-main-rollout.py b/tools/merge-main-rollout.py index 4848fbdfd..2f2afeeda 100644 --- a/tools/merge-main-rollout.py +++ b/tools/merge-main-rollout.py @@ -190,27 +190,56 @@ def remove_temp_worktree(path: str) -> None: shutil.rmtree(path, ignore_errors=True) -def merge_into_branch(branch: str, base: str, path: str) -> MergeResult: +def merge_in_progress(path: str) -> bool: + result = subprocess.run( + ["git", "rev-parse", "-q", "--verify", "MERGE_HEAD"], + cwd=path, + text=True, + capture_output=True, + ) + return result.returncode == 0 + + +def merge_into_branch(branch: str, base: str, path: str, allow_dirty: bool) -> MergeResult: before = run_capture(["git", "rev-parse", "HEAD"], cwd=path).stdout.strip() + dirty_before = worktree_dirty(path) merge_message = f"Merge {base} into {branch}\n\n{TRAILER}" + cmd = ["git", "merge", "--no-ff"] + if allow_dirty and dirty_before: + cmd.append("--autostash") + cmd.extend([base, "-m", merge_message]) result = subprocess.run( - ["git", "merge", "--no-ff", base, "-m", merge_message], + cmd, cwd=path, text=True, capture_output=True, ) if result.returncode != 0: - subprocess.run(["git", "merge", "--abort"], cwd=path, text=True, capture_output=True) + if merge_in_progress(path): + subprocess.run(["git", "merge", "--abort"], cwd=path, text=True, capture_output=True) + return MergeResult( + branch=branch, + status="conflict", + detail=(result.stderr.strip() or result.stdout.strip() or "merge failed"), + ) return MergeResult( branch=branch, - status="conflict", + status="restore-conflict" if dirty_before else "failed", detail=(result.stderr.strip() or result.stdout.strip() or "merge failed"), ) after = run_capture(["git", "rev-parse", "HEAD"], cwd=path).stdout.strip() if before == after: - return MergeResult(branch=branch, status="up-to-date", detail="already contained base") - return MergeResult(branch=branch, status="merged", detail=after) + status = "up-to-date-dirty" if dirty_before else "up-to-date" + detail = "already contained base" + if dirty_before and allow_dirty: + detail += " (dirty changes preserved with autostash)" + return MergeResult(branch=branch, status=status, detail=detail) + status = "merged-dirty" if dirty_before else "merged" + detail = after + if dirty_before and allow_dirty: + detail += " (dirty changes preserved with autostash)" + return MergeResult(branch=branch, status=status, detail=detail) def rollout( @@ -218,6 +247,7 @@ def rollout( recent_hours: float, pr_repos: Sequence[str], dry_run: bool, + dirty_mode: str, ) -> List[MergeResult]: targets = discover_targets(base, recent_hours, pr_repos) worktrees = get_worktrees() @@ -242,7 +272,8 @@ def rollout( path = existing_path try: if existing_path: - if worktree_dirty(existing_path): + dirty = worktree_dirty(existing_path) + if dirty and dirty_mode == "skip": results.append( MergeResult( branch=branch, @@ -252,22 +283,41 @@ def rollout( ) continue else: + if dry_run: + location = f" ({reason_text})" + results.append( + MergeResult(branch=branch, status="would-merge", detail=location) + ) + continue temp_path = ensure_temp_worktree(branch, temp_root) path = temp_path assert path is not None if dry_run: - location = existing_path if existing_path else temp_path + dirty = worktree_dirty(path) + if dirty and dirty_mode == "autostash": + status = "would-merge-dirty" + location = f"{path} ({reason_text}; autostash)" + else: + status = "would-merge" + location = f"{path} ({reason_text})" results.append( MergeResult( branch=branch, - status="would-merge", - detail=f"{location} ({reason_text})", + status=status, + detail=location, ) ) continue - results.append(merge_into_branch(branch, base, path)) + results.append( + merge_into_branch( + branch, + base, + path, + allow_dirty=(dirty_mode == "autostash"), + ) + ) finally: if temp_path is not None: remove_temp_worktree(temp_path) @@ -304,6 +354,12 @@ def build_parser() -> argparse.ArgumentParser: action="store_true", help="Discover and print targets without merging", ) + parser.add_argument( + "--dirty-mode", + choices=["autostash", "skip"], + default="autostash", + help="How to handle dirty checked-out worktrees (default: autostash)", + ) return parser @@ -316,6 +372,7 @@ def main() -> None: recent_hours=args.recent_hours, pr_repos=args.pr_repo, dry_run=args.dry_run, + dirty_mode=args.dirty_mode, ) print_results(results) except RolloutError as e: From e7f538205d204154c3c4a470af5578a015967031 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 18:59:07 +0100 Subject: [PATCH 013/372] Tune health checks and line ownership tooling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/execute/SKILL.md | 4 + .github/skills/implement/SKILL.md | 14 ++ AGENTS.md | 18 ++ tools/_common.py | 1 + tools/decomp-context.py | 1 + tools/decomp-workflow.py | 99 ++++++++- tools/dwarf-compare.py | 354 +++++++++++++++++++++++++++++- tools/line_lookup.py | 2 +- 8 files changed, 484 insertions(+), 9 deletions(-) diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index b2cf62247..58e81937e 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -32,10 +32,14 @@ the main worker after reviewing the read-only findings. Before any work begins, establish a regression baseline: ```sh +python tools/decomp-workflow.py health --full main/Path/To/TU ninja # ensure clean build ninja baseline # snapshot current match state ``` +Add `--timings` to the `health --full` command when you are investigating slow worktrees +or unexpectedly expensive build/tool startup. + After modifying shared headers, check `ninja changes` to verify no regressions were introduced. An empty changeset means no regressions. If regressions appear, the shared header change must be reverted. diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 1a68ccec5..25f56b926 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -26,6 +26,15 @@ functions unless the user explicitly wants a cleanup/refiner pass. Use the wrapper flow first throughout this skill. Drop to raw `decomp-context.py` or `decomp-diff.py` only when the wrapper is missing a specific flag or you are debugging. +On a new, suspicious, or recently updated worktree, start with: + +```sh +python tools/decomp-workflow.py health --full main/Path/To/TU +``` + +Add `--timings` when you need to understand why wrapper/tool startup or the shared build +smoke is slow. + ### 1a. decomp-context.py Preferred shortcut: @@ -189,6 +198,11 @@ python tools/decomp-workflow.py dwarf -u main/Path/To/TU -f FunctionName This gives you a normalized DWARF match percentage plus a diff-like report of what still differs between the original and rebuilt DWARF blocks for that function. +Pay attention to the `Range source ownership` summary there as well. It compares the +debug-line owner files for each DWARF `// Range:` block, which makes it much easier to +spot inlines that are coming from the wrong header or owner file. Exact line-number +agreement is a useful secondary hint, but file ownership is the first thing to check. + Manual fallback: After writing your code, you can also run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing diff --git a/AGENTS.md b/AGENTS.md index ff958229f..ca49784a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,6 +83,11 @@ originates from, use this script against the compiler-generated debug line mappi See `.github/skills/line_lookup/SKILL.md` for the full workflow. +`line_lookup.py` now accepts both the original `0xADDR:` debug-line format and rebuilt +object exports written as bare `ADDR:` lines, so you can point it at +`symbols/debug_lines.txt` or at a rebuilt `debug_lines.txt` from +`tools/dwarf1_gcc_line_info.py`. + ### code-style — Repo-local style guidance When you are writing code, polishing code you already touched, or doing a style-review pass, @@ -148,6 +153,8 @@ Prefer this wrapper for routine agent-driven flows instead of manually chaining ```sh python tools/decomp-workflow.py health +python tools/decomp-workflow.py health --full main/Speed/Indep/SourceLists/zAnim +python tools/decomp-workflow.py health --full main/Speed/Indep/SourceLists/zAnim --timings python tools/decomp-workflow.py health --smoke-build main/Speed/Indep/SourceLists/zAnim python tools/decomp-workflow.py health --smoke-dtk main/Speed/Indep/SourceLists/zAnim python tools/decomp-workflow.py next --category game --limit 10 @@ -168,6 +175,12 @@ repeated command chaining and to standardize routine worktree preflight checks f once when that output is missing, so wrapper-first inspection works more often on half-prepared worktrees. +Use `health --full ` when you want one end-to-end tooling smoke test for a worktree. +It reuses a single shared build for the build smoke, DTK dump, rebuilt debug-line export, +and rebuilt `line_lookup.py` check, so it is faster and more representative than chaining +`--smoke-build` and `--smoke-dtk` separately. Add `--timings` when you are diagnosing a +slow worktree or compiler/tool startup. + In normal agent work, use the wrapper commands first. Drop to the raw backend tools only when you specifically need a backend-only flag, are debugging a wrapper/backend discrepancy, or are doing a final exhaustive check that the wrapper does not expose directly. @@ -223,6 +236,11 @@ whenever `verify` says the function is still failing the DWARF gate. This is the fastest way to see whether you are still missing locals, have the wrong inline body, or changed signature/type details even when the instruction diff already looks good. +It also compares the debug-line ownership of each `// Range:` block. Treat the +`Range source ownership` summary as the fast inline-placement check: file mismatches are +strong evidence that an inline body came from the wrong header or owner file. The exact +file+line count is stricter and mainly useful as a secondary hint, not as the main gate. + When working with these tools, do not just work around recurring friction silently. If you notice a clear, safe workflow or tooling improvement that would make future decomp work faster, shorter, or more reliable, prefer implementing that improvement as part of the task diff --git a/tools/_common.py b/tools/_common.py index 2acfbac2a..985a122bc 100644 --- a/tools/_common.py +++ b/tools/_common.py @@ -20,6 +20,7 @@ "-c", "ppc.calculatePoolRelocations=false", ] +RELOC_DIFF_CHOICES = ("none", "function", "data", "all") class ToolError(RuntimeError): diff --git a/tools/decomp-context.py b/tools/decomp-context.py index c2382602b..b13790ece 100644 --- a/tools/decomp-context.py +++ b/tools/decomp-context.py @@ -25,6 +25,7 @@ import sys from typing import Any, Dict, List, Optional, Tuple from _common import ( + RELOC_DIFF_CHOICES, ROOT_DIR, ToolError, build_objdiff_symbol_rows, diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index 9193425cd..84f2f83d5 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -26,10 +26,12 @@ import re import os import shlex +import shutil import subprocess import sys import tempfile -from typing import Any, Dict, List, Optional, Sequence +import time +from typing import Any, Dict, List, Optional, Sequence, Tuple from _common import ( BUILD_NINJA, OBJDIFF_JSON, @@ -59,6 +61,7 @@ DEBUG_SYMBOL_PROBE_MANGLED = "UpdateAll__6Cameraf" DEBUG_SYMBOL_PROBE_DEMANGLED = "Camera::UpdateAll(float)" DEBUG_SYMBOL_PROBE_GC_ADDR = "0x80065A84" +REBUILT_DEBUG_LINE_RE = re.compile(r"^\s*([0-9A-Fa-f]+)\s*:") LOW_MATCH_PRIORITY_THRESHOLD = 60.0 VERY_LOW_MATCH_PRIORITY_THRESHOLD = 40.0 HIGH_MATCH_CLEANUP_THRESHOLD = 85.0 @@ -321,6 +324,11 @@ def lookup_symbol_address(symbols_file: str, mangled_name: str) -> Optional[str] def command_health(args: argparse.Namespace) -> None: failures = 0 + timings: List[Tuple[str, float]] = [] + build_cache: Dict[str, str] = {} + + smoke_build_unit = args.smoke_build or args.full + smoke_dtk_unit = args.smoke_dtk or args.full print_section("Worktree Health") print(f"Root: {ROOT_DIR}") @@ -332,6 +340,20 @@ def report(ok: bool, label: str, detail: str) -> None: if not ok: failures += 1 + def timed(label: str, func): + start = time.monotonic() + try: + return func() + finally: + timings.append((label, time.monotonic() - start)) + + def build_shared_unit_cached(unit: str) -> str: + if unit in build_cache: + return build_cache[unit] + output_path = timed(f"build {unit}", lambda: build_shared_unit(unit)) + build_cache[unit] = output_path + return output_path + report( os.path.exists(BUILD_NINJA), "build.ninja", @@ -368,7 +390,7 @@ def report(ok: bool, label: str, detail: str) -> None: DTK if os.path.exists(DTK) else "missing (seed build/tools in this worktree)", ) try: - run_capture(python_tool("decomp-context.py", "--ghidra-check")) + timed("ghidra-check", lambda: run_capture(python_tool("decomp-context.py", "--ghidra-check"))) report(True, "ghidra", "GC + PS2 programs available") except WorkflowError as e: report(False, "ghidra", str(e)) @@ -418,10 +440,10 @@ def report(ok: bool, label: str, detail: str) -> None: except WorkflowError as e: report(False, "debug-lines", str(e)) - if args.smoke_build: + if smoke_build_unit: print_section("Build Smoke Test") try: - output_path = build_shared_unit(args.smoke_build) + output_path = build_shared_unit_cached(smoke_build_unit) report(True, "build", output_path) except WorkflowError as e: detail = str(e) @@ -429,17 +451,65 @@ def report(ok: bool, label: str, detail: str) -> None: detail += "\nHint: Run: python tools/share_worktree_assets.py bootstrap" report(False, "build", detail) - if args.smoke_dtk: + if smoke_dtk_unit: print_section("DTK Smoke Test") dump_path = None + debug_lines_dir = None try: - obj_path = build_shared_unit(args.smoke_dtk) - dump_path = dtk_dwarf_dump(obj_path) + obj_path = build_shared_unit_cached(smoke_dtk_unit) + dump_path = timed(f"dtk dump {smoke_dtk_unit}", lambda: dtk_dwarf_dump(obj_path)) report(True, "dtk", dump_path) except WorkflowError as e: report(False, "dtk", str(e)) + else: + try: + debug_lines_dir = tempfile.mkdtemp(prefix="nfsmw_health_debug_lines_") + timed( + f"debug-line export {smoke_dtk_unit}", + lambda: run_capture( + python_tool("dwarf1_gcc_line_info.py", obj_path, debug_lines_dir) + ), + ) + rebuilt_debug_lines = os.path.join(debug_lines_dir, "debug_lines.txt") + if not os.path.exists(rebuilt_debug_lines): + raise WorkflowError( + "rebuilt debug-line export did not produce debug_lines.txt" + ) + first_address = None + with open(rebuilt_debug_lines) as f: + for raw_line in f: + match = REBUILT_DEBUG_LINE_RE.match(raw_line) + if match is not None: + first_address = match.group(1) + break + if first_address is None: + raise WorkflowError( + "rebuilt debug-line export produced no address entries" + ) + result = timed( + f"rebuilt line lookup {smoke_dtk_unit}", + lambda: run_capture( + python_tool("line_lookup.py", rebuilt_debug_lines, first_address) + ), + ) + ok = "Exact match found" in result.stdout + detail = ( + f"rebuilt line export ok ({first_address})" + if ok + else "rebuilt line lookup output did not contain an exact match" + ) + report(ok, "rebuilt-debug-lines", detail) + except WorkflowError as e: + report(False, "rebuilt-debug-lines", str(e)) finally: maybe_remove(dump_path) + if debug_lines_dir is not None: + shutil.rmtree(debug_lines_dir, ignore_errors=True) + + if args.timings and timings: + print_section("Timings") + for label, elapsed in timings: + print(f"{elapsed:7.2f}s {label}") if failures: raise WorkflowError(f"Health check failed with {failures} issue(s)") @@ -829,6 +899,16 @@ def build_parser() -> argparse.ArgumentParser: "health", help="Check whether the current worktree is ready for GC and PS2 decomp work", ) + health.add_argument( + "--full", + metavar="UNIT", + nargs="?", + const=DEFAULT_SMOKE_UNIT, + help=( + "Run the full smoke path for one unit: shared build, dtk dump, rebuilt " + f"debug-line export, and rebuilt line lookup. If UNIT is omitted, uses {DEFAULT_SMOKE_UNIT}" + ), + ) health.add_argument( "--smoke-build", metavar="UNIT", @@ -839,6 +919,11 @@ def build_parser() -> argparse.ArgumentParser: f"{DEFAULT_SMOKE_UNIT}" ), ) + health.add_argument( + "--timings", + action="store_true", + help="Show wall-clock timings for the heavier health-check steps", + ) health.add_argument( "--smoke-dtk", metavar="UNIT", diff --git a/tools/dwarf-compare.py b/tools/dwarf-compare.py index 51f35ea9b..087389d78 100644 --- a/tools/dwarf-compare.py +++ b/tools/dwarf-compare.py @@ -11,14 +11,19 @@ """ import argparse +import contextlib import difflib +import io import json import os import re +import shutil import sys +import tempfile from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple from _common import ROOT_DIR, ToolError, find_objdiff_unit, load_objdiff_config, make_abs +from dwarf1_gcc_line_info import process_file as export_debug_lines from lookup import ( _candidate_func_names, _normalise_func_name, @@ -33,6 +38,10 @@ GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") DTK = os.path.join(ROOT_DIR, "build", "tools", "dtk") HEX_RE = re.compile(r"0x[0-9A-Fa-f]+") +RANGE_RE = re.compile(r"^(\s*)// Range:\s*(0x[0-9A-Fa-f]+)\s*->\s*(0x[0-9A-Fa-f]+)") +DEBUG_LINE_RE = re.compile( + r"^\s*(?:0x)?([0-9A-Fa-f]+):\s*(.+?)\s+\(line\s+(\d+)(?:,\s+column\s+(\d+))?\)\s*$" +) class DwarfCompareError(RuntimeError): @@ -227,6 +236,269 @@ def normalize_block(block: str) -> List[str]: return [normalize_line(line) for line in block.splitlines()] +def canonical_debug_path(debug_path: str) -> str: + normalized = debug_path.replace("\\", "/").strip() + lowered = normalized.lower().replace("\\", "/") + if "/src/" in lowered: + src_index = lowered.index("/src/") + suffix = normalized[src_index + len("/src/") :].lstrip("/") + return os.path.normpath("src/" + suffix).replace("\\", "/") + if "/speed/indep/" in lowered: + indep_index = lowered.index("/speed/indep/") + suffix = normalized[indep_index + len("/speed/indep/") :].lstrip("/") + return os.path.normpath("src/Speed/Indep/" + suffix.lstrip("/")).replace("\\", "/") + return os.path.normpath(normalized).replace("\\", "/") + + +def normalize_source_location(path: str, line_number: int) -> str: + normalized = os.path.normpath(path.replace("\\", "/")).replace("\\", "/").lower() + return f"{normalized}:{line_number}" + + +def parse_debug_lines_file(path: str) -> Dict[int, List[Dict[str, Any]]]: + entries: Dict[int, List[Dict[str, Any]]] = {} + with open(path) as f: + for raw_line in f: + line = raw_line.rstrip("\n") + match = DEBUG_LINE_RE.match(line) + if match is None: + continue + address = int(match.group(1), 16) + debug_path = match.group(2) + line_number = int(match.group(3)) + display_path = canonical_debug_path(debug_path) + entries.setdefault(address, []).append( + { + "address": address, + "debug_path": debug_path, + "display_path": display_path, + "line_number": line_number, + "normalized_file": os.path.normpath(display_path.replace("\\", "/")) + .replace("\\", "/") + .lower(), + "normalized": normalize_source_location(display_path, line_number), + } + ) + return entries + + +def dedupe_source_locations(locations: Sequence[Dict[str, Any]]) -> List[str]: + deduped: List[str] = [] + seen = set() + for entry in locations: + rendered = f"{entry['display_path']}:{entry['line_number']}" + if rendered in seen: + continue + seen.add(rendered) + deduped.append(rendered) + return deduped + + +def dedupe_source_files(locations: Sequence[Dict[str, Any]]) -> List[str]: + deduped: List[str] = [] + seen = set() + for entry in locations: + normalized_file = entry["normalized_file"] + if normalized_file in seen: + continue + seen.add(normalized_file) + deduped.append(entry["display_path"]) + return deduped + + +def render_list(items: Sequence[str], limit: int = 6) -> str: + if not items: + return "" + if len(items) <= limit: + return ", ".join(items) + hidden = len(items) - limit + return f"{', '.join(items[:limit])}, ... (+{hidden} more)" + + +def build_debug_lines_file_for_object(obj_path: str) -> str: + temp_dir = tempfile.mkdtemp(prefix="nfsmw_debug_lines_") + with contextlib.redirect_stdout(io.StringIO()): + export_debug_lines(obj_path, temp_dir) + debug_lines_path = os.path.join(temp_dir, "debug_lines.txt") + if not os.path.exists(debug_lines_path): + raise DwarfCompareError("failed to export rebuilt debug lines") + return debug_lines_path + + +def collect_range_entries(block: str) -> List[Dict[str, Any]]: + lines = block.splitlines() + entries: List[Dict[str, Any]] = [] + for idx, line in enumerate(lines): + match = RANGE_RE.match(line) + if match is None: + continue + signature = "" + for follow in lines[idx + 1 :]: + stripped = follow.strip() + if not stripped or stripped.startswith("//"): + continue + signature = stripped.split("{")[0].strip() + break + entries.append( + { + "line_index": idx + 1, + "indent": len(match.group(1)) // 4, + "start_address": int(match.group(2), 16), + "end_address": int(match.group(3), 16), + "signature": signature, + } + ) + return entries + + +def normalized_signature_key(signature: str) -> str: + signature = signature.strip() + if not signature: + return "" + return normalize_line(signature) + + +def align_range_entries( + original_entries: Sequence[Dict[str, Any]], + rebuilt_entries: Sequence[Dict[str, Any]], +) -> List[Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]]: + original_keys = [ + f"{entry['indent']}|{normalized_signature_key(entry['signature'])}" for entry in original_entries + ] + rebuilt_keys = [ + f"{entry['indent']}|{normalized_signature_key(entry['signature'])}" for entry in rebuilt_entries + ] + matcher = difflib.SequenceMatcher(a=original_keys, b=rebuilt_keys) + aligned: List[Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]] = [] + for tag, i1, i2, j1, j2 in matcher.get_opcodes(): + if tag == "equal": + for orig, reb in zip(original_entries[i1:i2], rebuilt_entries[j1:j2]): + aligned.append((orig, reb)) + elif tag == "replace": + max_len = max(i2 - i1, j2 - j1) + for offset in range(max_len): + orig = original_entries[i1 + offset] if i1 + offset < i2 else None + reb = rebuilt_entries[j1 + offset] if j1 + offset < j2 else None + aligned.append((orig, reb)) + elif tag == "delete": + for orig in original_entries[i1:i2]: + aligned.append((orig, None)) + elif tag == "insert": + for reb in rebuilt_entries[j1:j2]: + aligned.append((None, reb)) + return aligned + + +def build_range_source_summary( + original_block: FunctionBlock, + rebuilt_block: FunctionBlock, + rebuilt_debug_lines_path: Optional[str], +) -> Dict[str, Any]: + if not rebuilt_debug_lines_path or not os.path.exists(rebuilt_debug_lines_path): + return {"available": False} + original_debug_lines_path = os.path.join(ROOT_DIR, "symbols", "debug_lines.txt") + if not os.path.exists(original_debug_lines_path): + return {"available": False} + + original_entries = collect_range_entries(original_block[3]) + rebuilt_entries = collect_range_entries(rebuilt_block[3]) + aligned_entries = align_range_entries(original_entries, rebuilt_entries) + original_debug_lines = parse_debug_lines_file(original_debug_lines_path) + rebuilt_debug_lines = parse_debug_lines_file(rebuilt_debug_lines_path) + + rows: List[Dict[str, Any]] = [] + file_match_count = 0 + exact_match_count = 0 + for original_entry, rebuilt_entry in aligned_entries: + original_items = ( + original_debug_lines.get(int(original_entry["start_address"]), []) + if original_entry is not None + else [] + ) + rebuilt_items = ( + rebuilt_debug_lines.get(int(rebuilt_entry["start_address"]), []) + if rebuilt_entry is not None + else [] + ) + original_locations = ( + dedupe_source_locations(original_items) if original_entry is not None else [] + ) + rebuilt_locations = ( + dedupe_source_locations(rebuilt_items) if rebuilt_entry is not None else [] + ) + original_files_display = ( + dedupe_source_files(original_items) if original_entry is not None else [] + ) + rebuilt_files_display = ( + dedupe_source_files(rebuilt_items) if rebuilt_entry is not None else [] + ) + original_norm = { + item["normalized"] + for item in original_items + } if original_entry is not None else set() + original_files = { + item["normalized_file"] + for item in original_items + } if original_entry is not None else set() + rebuilt_norm = { + item["normalized"] + for item in rebuilt_items + } if rebuilt_entry is not None else set() + rebuilt_files = { + item["normalized_file"] + for item in rebuilt_items + } if rebuilt_entry is not None else set() + common_files = [ + path + for path in original_files_display + if os.path.normpath(path.replace("\\", "/")).replace("\\", "/").lower() + in rebuilt_files + ] + original_only_files = [ + path + for path in original_files_display + if os.path.normpath(path.replace("\\", "/")).replace("\\", "/").lower() + not in rebuilt_files + ] + rebuilt_only_files = [ + path + for path in rebuilt_files_display + if os.path.normpath(path.replace("\\", "/")).replace("\\", "/").lower() + not in original_files + ] + if original_entry is not None and rebuilt_entry is not None and original_files == rebuilt_files: + file_status = "match" + file_match_count += 1 + else: + file_status = "mismatch" + if original_entry is not None and rebuilt_entry is not None and original_norm == rebuilt_norm: + exact_status = "match" + exact_match_count += 1 + else: + exact_status = "mismatch" + rows.append( + { + "file_status": file_status, + "exact_status": exact_status, + "indent": (original_entry or rebuilt_entry or {}).get("indent", 0), + "line_number": (original_entry or rebuilt_entry or {}).get("line_index"), + "signature": (original_entry or rebuilt_entry or {}).get("signature", ""), + "original_locations": original_locations, + "rebuilt_locations": rebuilt_locations, + "common_files": common_files, + "original_only_files": original_only_files, + "rebuilt_only_files": rebuilt_only_files, + } + ) + return { + "available": True, + "rows": rows, + "total": len(rows), + "file_matches": file_match_count, + "exact_matches": exact_match_count, + } + + def count_lines_for_opcodes(opcodes: Sequence[Tuple[str, int, int, int, int]]) -> Dict[str, int]: matching = 0 original_only = 0 @@ -297,6 +569,7 @@ def build_report( rebuilt_block: FunctionBlock, collapse: bool, context: int, + rebuilt_debug_lines_path: Optional[str], ) -> Dict[str, Any]: original_raw = original_block[3].splitlines() rebuilt_raw = rebuilt_block[3].splitlines() @@ -345,6 +618,12 @@ def build_report( f"- {original_span} -> {rebuilt_span}: {detail}" ) + range_sources = build_range_source_summary( + original_block, + rebuilt_block, + rebuilt_debug_lines_path=rebuilt_debug_lines_path, + ) + return { "unit": unit_name, "function": function_name, @@ -364,6 +643,7 @@ def build_report( "original_range": [original_block[0], original_block[1]], "rebuilt_range": [rebuilt_block[0], rebuilt_block[1]], "mismatch_summaries": mismatch_summaries, + "range_sources": range_sources, "diff_lines": diff_lines, } @@ -392,6 +672,17 @@ def print_summary(report: Dict[str, Any]) -> None: "Address-only range differences are normalized out so the percentage tracks " "structural/function-body DWARF changes." ) + if report["range_sources"].get("available"): + line_drifts = report["range_sources"]["file_matches"] - report["range_sources"]["exact_matches"] + print( + f"Range source ownership: files agree {report['range_sources']['file_matches']}/" + f"{report['range_sources']['total']}" + f" | file mismatches {report['range_sources']['total'] - report['range_sources']['file_matches']}/" + f"{report['range_sources']['total']}" + f" | line drifts {line_drifts}/{report['range_sources']['total']}" + ) + else: + print("Range source ownership: unavailable (missing debug-line export data)") if not report["signature_match"]: print() print("Original signature:") @@ -401,6 +692,55 @@ def print_summary(report: Dict[str, Any]) -> None: def print_diff(report: Dict[str, Any]) -> None: + if not report["range_sources"].get("available"): + file_mismatches = [] + line_drifts = [] + elif report["range_sources"]["rows"]: + file_mismatches = [ + row for row in report["range_sources"]["rows"] if row["file_status"] != "match" + ] + line_drifts = [ + row + for row in report["range_sources"]["rows"] + if row["file_status"] == "match" and row["exact_status"] != "match" + ] + else: + file_mismatches = [] + line_drifts = [] + + if file_mismatches: + print_section("Range Source Ownership") + for row in file_mismatches: + location = f"L{row['line_number']:04d}" if row["line_number"] else "L????" + print(f"- {location} {row['signature']}") + if row["common_files"]: + print(f" common files: {render_list(row['common_files'])}") + if row["original_only_files"]: + print( + f" original-only files: {render_list(row['original_only_files'])}" + ) + if row["rebuilt_only_files"]: + print( + f" rebuilt-only files: {render_list(row['rebuilt_only_files'])}" + ) + print( + f" original lines: {render_list(row['original_locations'])}" + ) + print( + f" rebuilt lines: {render_list(row['rebuilt_locations'])}" + ) + if line_drifts: + print() + print( + f"Additionally, {len(line_drifts)}/{report['range_sources']['total']} ranges keep " + "the same source files but drift on line numbers." + ) + elif line_drifts: + print_section("Range Source Ownership") + print( + "All range starts resolve to the same source files; line numbers drift " + f"for {len(line_drifts)}/{report['range_sources']['total']} ranges." + ) if report["mismatch_summaries"]: print_section("Mismatch Summary") for line in report["mismatch_summaries"]: @@ -463,12 +803,14 @@ def main() -> None: args = parser.parse_args() rebuilt_dwarf_path: Optional[str] = None + rebuilt_debug_lines_path: Optional[str] = None cleanup_rebuilt_dwarf = False + cleanup_rebuilt_debug_lines_dir = False try: + obj_path = get_unit_build_output(args.unit) if args.rebuilt_dwarf_file: rebuilt_dwarf_path = os.path.abspath(args.rebuilt_dwarf_file) else: - obj_path = get_unit_build_output(args.unit) if not os.path.exists(obj_path): raise DwarfCompareError( f"Missing built object for {args.unit}: {obj_path}\n" @@ -478,6 +820,13 @@ def main() -> None: rebuilt_dwarf_path = dtk_dwarf_dump(obj_path) cleanup_rebuilt_dwarf = True + if os.path.exists(obj_path): + try: + rebuilt_debug_lines_path = build_debug_lines_file_for_object(obj_path) + cleanup_rebuilt_debug_lines_dir = True + except Exception: + rebuilt_debug_lines_path = None + original_funcs = load_function_blocks(GC_DWARF, folder_mode=True) rebuilt_funcs = load_function_blocks(rebuilt_dwarf_path, folder_mode=False) @@ -491,6 +840,7 @@ def main() -> None: rebuilt_block, collapse=not args.no_collapse, context=args.context, + rebuilt_debug_lines_path=rebuilt_debug_lines_path, ) if args.json: @@ -511,6 +861,8 @@ def main() -> None: finally: if cleanup_rebuilt_dwarf: maybe_remove(rebuilt_dwarf_path) + if cleanup_rebuilt_debug_lines_dir and rebuilt_debug_lines_path: + shutil.rmtree(os.path.dirname(rebuilt_debug_lines_path), ignore_errors=True) if __name__ == "__main__": diff --git a/tools/line_lookup.py b/tools/line_lookup.py index 65aed6649..41890b877 100644 --- a/tools/line_lookup.py +++ b/tools/line_lookup.py @@ -21,7 +21,7 @@ def parse_map_file(filepath): with open(filepath, "r") as f: for line in f: line = line.rstrip("\n") - match = re.match(r"^\s*(0x[0-9A-Fa-f]+)\s*:", line) + match = re.match(r"^\s*((?:0x)?[0-9A-Fa-f]+)\s*:", line) if match: addr = int(match.group(1), 16) entries.append((addr, line)) From 6ac3c90906aba8b8db2298c69f1869362d4bc4b7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 19:10:44 +0100 Subject: [PATCH 014/372] Add ELF address lookup tool Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/execute/SKILL.md | 1 + .github/skills/refiner/SKILL.md | 2 +- AGENTS.md | 16 +++ tools/elf_lookup.py | 174 ++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 tools/elf_lookup.py diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 58e81937e..7abf9cb3f 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -115,6 +115,7 @@ For each missing or nonmatching function, follow the implementation workflow in implementing the next function reveals patterns that make the previous one click. - **Mismatch triage:** - `@stringBase0` offset mismatches often resolve as more string literals are added + - If you need to inspect the original string or rodata at a virtual address, use `python tools/elf_lookup.py 0xADDR` - Register swaps and stack layout issues require direct intervention - Branch structure mismatches indicate wrong control flow (if/switch/loop) - **Match percentage is misleading.** The last few percent are often the hardest. diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index 4d1fe1bb7..a6aeb2125 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -46,7 +46,7 @@ Read every instruction pair. Categorize each mismatch: | **Stack frame size** | Wrong frame size in prologue | Count locals in DWARF; remove temporaries not in DWARF | | **Float vs int sequence** | `xoris` present → field is `int`; absent → `uint` | Check field type in DWARF; change cast | | **`fmuls` operand order** | `fmuls fX, fX, fY` or `fmuls fX, fY, fX` | Try `v *= fY` vs `fY * v` explicitly | -| **Relocation offset** | `@stringBase0` or data offset differs | More string literals will shift this; add them in order | +| **Relocation offset** | `@stringBase0` or data offset differs | More string literals will shift this; add them in order. Use `python tools/elf_lookup.py 0xADDR` when you need to confirm the original string/rodata at a virtual address | | **Virtual vs direct call** | `bl` vs indirect through vtable | Check const-qualifier; use `GetFoo()` vs `Foo()` | | **Inline vs outlined** | Extra call to helper vs inlined sequence | Force inline by rewriting the expression without calling the helper | | **Loop structure** | Guarded `do/while` from Ghidra or mismatched loop branches | Rewrite to the natural source form suggested by the control flow; in particular, a guarded `do/while` often needs to become a plain `for` loop | diff --git a/AGENTS.md b/AGENTS.md index ca49784a2..bd8a1ec3a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,6 +88,20 @@ object exports written as bare `ADDR:` lines, so you can point it at `symbols/debug_lines.txt` or at a rebuilt `debug_lines.txt` from `tools/dwarf1_gcc_line_info.py`. +### elf_lookup.py — Resolve strings / rodata by virtual address + +When you have a virtual address inside the original ELF and need to know which string or +rodata bytes live there, use: + +```sh +python tools/elf_lookup.py 0x803E58F4 +python tools/elf_lookup.py 0x803E58F4 --mode bytes --length 32 +python tools/elf_lookup.py 0x002F1234 --game ps2 +``` + +This is the preferred replacement for ad-hoc Python snippets that manually parse the ELF +to chase `@stringBase0` or other rodata/data references. + ### code-style — Repo-local style guidance When you are writing code, polishing code you already touched, or doing a style-review pass, @@ -434,6 +448,8 @@ It's very important that you use math inlines from bMath and UMath as shown in t - When you have to use a constant that looks like an address, it's possible that the splitter thought it was an allocation and it shows up as a diff because the left side has a symbol and the right side has a constant. In this case you need to figure out the virtual address of the instruction and block the relocation in config.yml. +- When you need to confirm what lives at a rodata/data address from the original ELF, use + `python tools/elf_lookup.py 0xADDR` instead of writing a one-off Python script. ### PPC EABI calling convention diff --git a/tools/elf_lookup.py b/tools/elf_lookup.py new file mode 100644 index 000000000..de0840a15 --- /dev/null +++ b/tools/elf_lookup.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Look up string or raw bytes in an ELF by virtual address. + +Examples: + python tools/elf_lookup.py 0x803E58F4 + python tools/elf_lookup.py 803E58F4 --mode bytes --length 32 + python tools/elf_lookup.py 0x002F1234 --game ps2 + python tools/elf_lookup.py 0x803E58F4 --elf orig/GOWE69/NFSMWRELEASE.ELF +""" + +import argparse +import os +import string +import sys +from typing import Any, Dict, Optional + +from elftools.elf.elffile import ELFFile + +from _common import ROOT_DIR, ToolError + + +DEFAULT_ELF_BY_GAME = { + "gc": os.path.join(ROOT_DIR, "orig", "GOWE69", "NFSMWRELEASE.ELF"), + "ps2": os.path.join(ROOT_DIR, "orig", "SLES-53558-A124", "NFS.ELF"), +} + + +def parse_address(raw: str) -> int: + try: + return int(raw, 16) + except ValueError as exc: + raise ToolError(f"invalid hex address: {raw}") from exc + + +def choose_elf_path(args: argparse.Namespace) -> str: + if args.elf: + path = args.elf + if not os.path.isabs(path): + path = os.path.join(ROOT_DIR, path) + return os.path.abspath(path) + return DEFAULT_ELF_BY_GAME[args.game] + + +def find_section_for_address(elffile: ELFFile, address: int) -> Optional[Dict[str, Any]]: + for section in elffile.iter_sections(): + header = section.header + start = int(header["sh_addr"]) + size = int(header["sh_size"]) + if size <= 0: + continue + end = start + size + if start <= address < end: + return { + "name": section.name, + "address": start, + "size": size, + "offset": int(header["sh_offset"]), + "data": section.data(), + } + return None + + +def read_c_string(data: bytes, start_offset: int, max_bytes: int) -> bytes: + blob = data[start_offset : start_offset + max_bytes] + terminator = blob.find(b"\x00") + if terminator != -1: + blob = blob[:terminator] + return blob + + +def printable_ratio(blob: bytes) -> float: + if not blob: + return 1.0 + printable = set(string.printable.encode("ascii")) + hits = sum(1 for byte in blob if byte in printable and byte not in b"\x0b\x0c") + return hits / len(blob) + + +def decode_display_string(blob: bytes) -> str: + try: + return blob.decode("utf-8") + except UnicodeDecodeError: + return blob.decode("latin-1", errors="replace") + + +def format_hex_bytes(blob: bytes, width: int = 16) -> str: + lines = [] + for offset in range(0, len(blob), width): + chunk = blob[offset : offset + width] + hex_part = " ".join(f"{byte:02X}" for byte in chunk) + ascii_part = "".join(chr(byte) if 32 <= byte <= 126 else "." for byte in chunk) + lines.append(f" +0x{offset:04X}: {hex_part:<{width * 3}} {ascii_part}") + return "\n".join(lines) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Look up a string or raw bytes in an ELF by virtual address." + ) + parser.add_argument("address", help="Virtual address, with or without 0x prefix") + parser.add_argument( + "--game", + choices=sorted(DEFAULT_ELF_BY_GAME), + default="gc", + help="Shortcut for selecting the default GameCube or PS2 ELF (default: gc)", + ) + parser.add_argument( + "--elf", + help="Explicit ELF path. Overrides --game.", + ) + parser.add_argument( + "--mode", + choices=["string", "bytes"], + default="string", + help="Read a C string or raw bytes from the address (default: string)", + ) + parser.add_argument( + "--length", + type=int, + default=64, + help="Maximum bytes to read (default: 64)", + ) + args = parser.parse_args() + + address = parse_address(args.address) + elf_path = choose_elf_path(args) + if not os.path.exists(elf_path): + raise ToolError(f"ELF not found: {elf_path}") + + with open(elf_path, "rb") as f: + elffile = ELFFile(f) + section = find_section_for_address(elffile, address) + + if section is None: + raise ToolError( + f"address 0x{address:08X} is not inside any ELF section in {elf_path}" + ) + + section_offset = address - int(section["address"]) + file_offset = int(section["offset"]) + section_offset + section_data = section["data"] + + print(f"ELF: {os.path.relpath(elf_path, ROOT_DIR)}") + print(f"Address: 0x{address:08X}") + print( + f"Section: {section['name']} +0x{section_offset:X} " + f"(section VA 0x{int(section['address']):08X}, file offset 0x{file_offset:X})" + ) + + if args.mode == "string": + blob = read_c_string(section_data, section_offset, args.length) + display = decode_display_string(blob) + kind = "string" if printable_ratio(blob) >= 0.85 else "non-printable bytes" + print(f"Kind: {kind}") + print(f"Length: {len(blob)} byte(s)") + print(f"Value: {display!r}") + if blob: + print("Hex:") + print(format_hex_bytes(blob)) + return + + blob = section_data[section_offset : section_offset + args.length] + print(f"Length: {len(blob)} byte(s)") + print("Hex:") + print(format_hex_bytes(blob)) + + +if __name__ == "__main__": + try: + main() + except ToolError as exc: + print(f"Error: {exc}", file=sys.stderr) + sys.exit(1) From dfd359ec701c47d115d6266ed9fedecf45d47e34 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 19:10:44 +0100 Subject: [PATCH 015/372] Add ELF address lookup tool Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/execute/SKILL.md | 1 + .github/skills/refiner/SKILL.md | 2 +- AGENTS.md | 16 +++ tools/elf_lookup.py | 174 ++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 tools/elf_lookup.py diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 58e81937e..7abf9cb3f 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -115,6 +115,7 @@ For each missing or nonmatching function, follow the implementation workflow in implementing the next function reveals patterns that make the previous one click. - **Mismatch triage:** - `@stringBase0` offset mismatches often resolve as more string literals are added + - If you need to inspect the original string or rodata at a virtual address, use `python tools/elf_lookup.py 0xADDR` - Register swaps and stack layout issues require direct intervention - Branch structure mismatches indicate wrong control flow (if/switch/loop) - **Match percentage is misleading.** The last few percent are often the hardest. diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index 4d1fe1bb7..a6aeb2125 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -46,7 +46,7 @@ Read every instruction pair. Categorize each mismatch: | **Stack frame size** | Wrong frame size in prologue | Count locals in DWARF; remove temporaries not in DWARF | | **Float vs int sequence** | `xoris` present → field is `int`; absent → `uint` | Check field type in DWARF; change cast | | **`fmuls` operand order** | `fmuls fX, fX, fY` or `fmuls fX, fY, fX` | Try `v *= fY` vs `fY * v` explicitly | -| **Relocation offset** | `@stringBase0` or data offset differs | More string literals will shift this; add them in order | +| **Relocation offset** | `@stringBase0` or data offset differs | More string literals will shift this; add them in order. Use `python tools/elf_lookup.py 0xADDR` when you need to confirm the original string/rodata at a virtual address | | **Virtual vs direct call** | `bl` vs indirect through vtable | Check const-qualifier; use `GetFoo()` vs `Foo()` | | **Inline vs outlined** | Extra call to helper vs inlined sequence | Force inline by rewriting the expression without calling the helper | | **Loop structure** | Guarded `do/while` from Ghidra or mismatched loop branches | Rewrite to the natural source form suggested by the control flow; in particular, a guarded `do/while` often needs to become a plain `for` loop | diff --git a/AGENTS.md b/AGENTS.md index ca49784a2..bd8a1ec3a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,6 +88,20 @@ object exports written as bare `ADDR:` lines, so you can point it at `symbols/debug_lines.txt` or at a rebuilt `debug_lines.txt` from `tools/dwarf1_gcc_line_info.py`. +### elf_lookup.py — Resolve strings / rodata by virtual address + +When you have a virtual address inside the original ELF and need to know which string or +rodata bytes live there, use: + +```sh +python tools/elf_lookup.py 0x803E58F4 +python tools/elf_lookup.py 0x803E58F4 --mode bytes --length 32 +python tools/elf_lookup.py 0x002F1234 --game ps2 +``` + +This is the preferred replacement for ad-hoc Python snippets that manually parse the ELF +to chase `@stringBase0` or other rodata/data references. + ### code-style — Repo-local style guidance When you are writing code, polishing code you already touched, or doing a style-review pass, @@ -434,6 +448,8 @@ It's very important that you use math inlines from bMath and UMath as shown in t - When you have to use a constant that looks like an address, it's possible that the splitter thought it was an allocation and it shows up as a diff because the left side has a symbol and the right side has a constant. In this case you need to figure out the virtual address of the instruction and block the relocation in config.yml. +- When you need to confirm what lives at a rodata/data address from the original ELF, use + `python tools/elf_lookup.py 0xADDR` instead of writing a one-off Python script. ### PPC EABI calling convention diff --git a/tools/elf_lookup.py b/tools/elf_lookup.py new file mode 100644 index 000000000..de0840a15 --- /dev/null +++ b/tools/elf_lookup.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Look up string or raw bytes in an ELF by virtual address. + +Examples: + python tools/elf_lookup.py 0x803E58F4 + python tools/elf_lookup.py 803E58F4 --mode bytes --length 32 + python tools/elf_lookup.py 0x002F1234 --game ps2 + python tools/elf_lookup.py 0x803E58F4 --elf orig/GOWE69/NFSMWRELEASE.ELF +""" + +import argparse +import os +import string +import sys +from typing import Any, Dict, Optional + +from elftools.elf.elffile import ELFFile + +from _common import ROOT_DIR, ToolError + + +DEFAULT_ELF_BY_GAME = { + "gc": os.path.join(ROOT_DIR, "orig", "GOWE69", "NFSMWRELEASE.ELF"), + "ps2": os.path.join(ROOT_DIR, "orig", "SLES-53558-A124", "NFS.ELF"), +} + + +def parse_address(raw: str) -> int: + try: + return int(raw, 16) + except ValueError as exc: + raise ToolError(f"invalid hex address: {raw}") from exc + + +def choose_elf_path(args: argparse.Namespace) -> str: + if args.elf: + path = args.elf + if not os.path.isabs(path): + path = os.path.join(ROOT_DIR, path) + return os.path.abspath(path) + return DEFAULT_ELF_BY_GAME[args.game] + + +def find_section_for_address(elffile: ELFFile, address: int) -> Optional[Dict[str, Any]]: + for section in elffile.iter_sections(): + header = section.header + start = int(header["sh_addr"]) + size = int(header["sh_size"]) + if size <= 0: + continue + end = start + size + if start <= address < end: + return { + "name": section.name, + "address": start, + "size": size, + "offset": int(header["sh_offset"]), + "data": section.data(), + } + return None + + +def read_c_string(data: bytes, start_offset: int, max_bytes: int) -> bytes: + blob = data[start_offset : start_offset + max_bytes] + terminator = blob.find(b"\x00") + if terminator != -1: + blob = blob[:terminator] + return blob + + +def printable_ratio(blob: bytes) -> float: + if not blob: + return 1.0 + printable = set(string.printable.encode("ascii")) + hits = sum(1 for byte in blob if byte in printable and byte not in b"\x0b\x0c") + return hits / len(blob) + + +def decode_display_string(blob: bytes) -> str: + try: + return blob.decode("utf-8") + except UnicodeDecodeError: + return blob.decode("latin-1", errors="replace") + + +def format_hex_bytes(blob: bytes, width: int = 16) -> str: + lines = [] + for offset in range(0, len(blob), width): + chunk = blob[offset : offset + width] + hex_part = " ".join(f"{byte:02X}" for byte in chunk) + ascii_part = "".join(chr(byte) if 32 <= byte <= 126 else "." for byte in chunk) + lines.append(f" +0x{offset:04X}: {hex_part:<{width * 3}} {ascii_part}") + return "\n".join(lines) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Look up a string or raw bytes in an ELF by virtual address." + ) + parser.add_argument("address", help="Virtual address, with or without 0x prefix") + parser.add_argument( + "--game", + choices=sorted(DEFAULT_ELF_BY_GAME), + default="gc", + help="Shortcut for selecting the default GameCube or PS2 ELF (default: gc)", + ) + parser.add_argument( + "--elf", + help="Explicit ELF path. Overrides --game.", + ) + parser.add_argument( + "--mode", + choices=["string", "bytes"], + default="string", + help="Read a C string or raw bytes from the address (default: string)", + ) + parser.add_argument( + "--length", + type=int, + default=64, + help="Maximum bytes to read (default: 64)", + ) + args = parser.parse_args() + + address = parse_address(args.address) + elf_path = choose_elf_path(args) + if not os.path.exists(elf_path): + raise ToolError(f"ELF not found: {elf_path}") + + with open(elf_path, "rb") as f: + elffile = ELFFile(f) + section = find_section_for_address(elffile, address) + + if section is None: + raise ToolError( + f"address 0x{address:08X} is not inside any ELF section in {elf_path}" + ) + + section_offset = address - int(section["address"]) + file_offset = int(section["offset"]) + section_offset + section_data = section["data"] + + print(f"ELF: {os.path.relpath(elf_path, ROOT_DIR)}") + print(f"Address: 0x{address:08X}") + print( + f"Section: {section['name']} +0x{section_offset:X} " + f"(section VA 0x{int(section['address']):08X}, file offset 0x{file_offset:X})" + ) + + if args.mode == "string": + blob = read_c_string(section_data, section_offset, args.length) + display = decode_display_string(blob) + kind = "string" if printable_ratio(blob) >= 0.85 else "non-printable bytes" + print(f"Kind: {kind}") + print(f"Length: {len(blob)} byte(s)") + print(f"Value: {display!r}") + if blob: + print("Hex:") + print(format_hex_bytes(blob)) + return + + blob = section_data[section_offset : section_offset + args.length] + print(f"Length: {len(blob)} byte(s)") + print("Hex:") + print(format_hex_bytes(blob)) + + +if __name__ == "__main__": + try: + main() + except ToolError as exc: + print(f"Error: {exc}", file=sys.stderr) + sys.exit(1) From 2dcd3a08c115dd4cf0bfba767a8849c55eb98007 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 19:15:37 +0100 Subject: [PATCH 016/372] Fix ProDG dep path rewriting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/transform_dep.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/tools/transform_dep.py b/tools/transform_dep.py index 124de04b9..36dbea004 100755 --- a/tools/transform_dep.py +++ b/tools/transform_dep.py @@ -13,6 +13,7 @@ import argparse import os +import re from platform import uname wineprefix = os.path.join(os.environ["HOME"], ".wine") @@ -27,6 +28,7 @@ def in_wsl() -> bool: def import_d_file(in_file: str) -> str: out_text = "" + build_root = os.getcwd() with open(in_file) as file: for idx, line in enumerate(file): @@ -42,19 +44,26 @@ def import_d_file(in_file: str) -> str: path = line.lstrip()[:-3] else: path = line.strip() - # lowercase drive letter - path = path[0].lower() + path[1:] - if path[0] == "z": - # shortcut for z: - path = path[2:].replace("\\", "/") - elif in_wsl(): - path = path[0:1] + path[2:] - path = os.path.join("/mnt", path.replace("\\", "/")) + path = path.replace("\\", "/") + if re.match(r"^[A-Za-z]:", path): + # lowercase drive letter for Windows-style absolute paths + path = path[0].lower() + path[1:] + if path[0] == "z": + # shortcut for z: + path = path[2:] + elif in_wsl(): + path = path[0:1] + path[2:] + path = os.path.join("/mnt", path) + else: + # use $WINEPREFIX/dosdevices to resolve path + path = os.path.realpath(os.path.join(winedevices, path)) + elif os.path.isabs(path): + path = os.path.realpath(path) else: - # use $WINEPREFIX/dosdevices to resolve path - path = os.path.realpath( - os.path.join(winedevices, path.replace("\\", "/")) - ) + # ProDG often emits repo-relative includes like src\Foo.h. + # Keep them anchored to the current build root instead of + # incorrectly treating them as Wine drive roots. + path = os.path.realpath(os.path.join(build_root, path)) out_text += "\t" + path + suffix + "\n" return out_text From 4aafb334420e5334a323abb42cff5070c9ae3e95 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 19:43:04 +0100 Subject: [PATCH 017/372] Clarify formatter allowlist semantics Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 8 ++++---- AGENTS.md | 8 ++++++-- README.md | 2 +- tools/code_style.py | 16 +++++++++------- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 950d7c6ee..2c9ba5f19 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -27,14 +27,14 @@ Use the repo-local helper before doing a style pass: python tools/code_style.py audit --base origin/main ``` -- `audit` classifies changed files into safe vs match-sensitive buckets and reports repo-specific findings. +- `audit` classifies changed files into default-format vs match-sensitive buckets and reports repo-specific findings. - `audit` also checks touched `class` / `struct` declarations against known header declarations and, when no header exists, against the PS2 visibility rule. - `audit` warns on touched local forward declarations when the repo already has a header for that type. - `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. - `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. - `audit` groups repeated findings by file so branch-wide output stays readable. -- Use `audit --category safe-cpp` for frontend/support cleanup passes and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. -- `format --check` is an opt-in wrapper around the repo's `.clang-format`, but it only targets safe C/C++ files by default. +- Use `audit --category safe-cpp` for the tool's default-format frontend/support bucket and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. +- `format --check` is an opt-in wrapper around the repo's `.clang-format`, but it only targets the tool's default allowlisted C/C++ files by default. - Use `format --check --base origin/main --category safe-cpp` when you want a branch-level formatter probe instead of spelling every file path out. - `format --check` labels whitespace-only formatter output separately from more invasive changes such as include reordering. - `format` never targets `SourceLists/z*.cpp`; those files stay audit-only even when you opt into risky formatting. @@ -56,7 +56,7 @@ Examples: For these files, style cleanup must be conservative and verified. -### 1b. Safer support / frontend / tooling code +### 1b. Default-format support / frontend / tooling code Examples: diff --git a/AGENTS.md b/AGENTS.md index bd8a1ec3a..95cd388f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,8 +110,8 @@ cleanup rules, including jumbo include spacing, initializer-list comment markers placement, pointer style, and how to keep style work safe in match-sensitive code. Use `python tools/code_style.py audit --base origin/main` before a branch-wide style pass. -It classifies changed files, reports repo-specific findings, and only treats safer C/C++ files -as clang-format candidates by default. +It classifies changed files, reports repo-specific findings, and only treats a narrow +allowlisted subset of C/C++ files as clang-format candidates by default. ### decomp-diff.py — Diff & symbol overview @@ -238,6 +238,10 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName - objdiff instruction match is 100% - normalized DWARF block match is exact +Pass the normal demangled function name to `verify`. The objdiff side matches against both +the demangled and symbol-name fields, and the DWARF side reuses the demangled-name lookup +matching that also tolerates omitted leading namespaces when that information is inconsistent. + If the combined check fails, then inspect the DWARF diff directly with: ```sh diff --git a/README.md b/README.md index e36bd8a4e..d0103a8d1 100644 --- a/README.md +++ b/README.md @@ -287,7 +287,7 @@ If you have `clang-format` installed locally, you can also use: python tools/code_style.py format --check src/Speed/Indep/Src/Frontend/FEManager.cpp ``` -The formatter wrapper only targets safer C/C++ files by default. It intentionally skips match-sensitive code unless you explicitly pass `--include-match-sensitive` and verify the affected unit afterwards. +The formatter wrapper only targets a narrow allowlisted subset of C/C++ files by default. That allowlist is about limiting churn, not about whitespace changing codegen by itself. The risky cases are formatter-driven changes such as include reordering and files that rely on the repo's initializer-list guard comments, so match-sensitive code is still skipped unless you explicitly pass `--include-match-sensitive` and verify the affected unit afterwards. `SourceLists/z*.cpp` files remain audit-only and are never formatter targets. `format --check` now distinguishes whitespace-only formatter deltas from more invasive output such as include reordering. Files that use the repo's initializer-list guard comments (`//`) are skipped by default because clang-format fights that convention; override only if you are deliberately inspecting that output. diff --git a/tools/code_style.py b/tools/code_style.py index a1a8f83fa..09be61f20 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -26,7 +26,9 @@ CPP_EXTS = {".c", ".cc", ".cpp", ".h", ".hh", ".hpp"} HEADER_EXTS = {".h", ".hh", ".hpp"} -SAFE_CPP_PREFIXES = ( +# Default clang-format allowlist. This is about limiting churn, not guaranteeing +# byte-match safety. +DEFAULT_FORMAT_CPP_PREFIXES = ( "src/Speed/Indep/Src/Frontend/", "src/Speed/Indep/Src/FEng/", ) @@ -132,7 +134,7 @@ def path_category(path: str) -> str: return "tooling" if path.startswith(JUMBO_PREFIX): return "jumbo-source-list" - if any(path.startswith(prefix) for prefix in SAFE_CPP_PREFIXES): + if any(path.startswith(prefix) for prefix in DEFAULT_FORMAT_CPP_PREFIXES): return "safe-cpp" if ext in CPP_EXTS else "safe-other" if any(path.startswith(prefix) for prefix in MATCH_SENSITIVE_PREFIXES): return "match-sensitive-cpp" if ext in CPP_EXTS else "match-sensitive-other" @@ -692,14 +694,14 @@ def command_audit(args: argparse.Namespace) -> int: print(f" {category}: {len(by_category[category])}") print() - safe_format_candidates = [ + default_format_candidates = [ path for path in paths if path_category(path) == "safe-cpp" and os.path.splitext(path)[1] in CPP_EXTS ] - if safe_format_candidates: - print("Safe clang-format candidates:") - for path in safe_format_candidates: + if default_format_candidates: + print("Default clang-format candidates:") + for path in default_format_candidates: print(f" {path}") print() @@ -923,7 +925,7 @@ def build_parser() -> argparse.ArgumentParser: fmt = subparsers.add_parser( "format", parents=[shared], - help="Run clang-format on safe files by default", + help="Run clang-format on the default allowlisted files by default", ) fmt.add_argument( "--category", From 71946bdd2428673716a6f233a16b302c7a5f533c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 19:58:15 +0100 Subject: [PATCH 018/372] Clarify formatter default scope Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 6 +++--- AGENTS.md | 4 ++-- README.md | 6 +++--- tools/code_style.py | 7 ++++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 2c9ba5f19..d6a13c738 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -33,12 +33,12 @@ python tools/code_style.py audit --base origin/main - `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. - `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. - `audit` groups repeated findings by file so branch-wide output stays readable. -- Use `audit --category safe-cpp` for the tool's default-format frontend/support bucket and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. +- Use `audit --category safe-cpp` for the tool's intentionally tiny Frontend/FEng default-format bucket and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. - `format --check` is an opt-in wrapper around the repo's `.clang-format`, but it only targets the tool's default allowlisted C/C++ files by default. - Use `format --check --base origin/main --category safe-cpp` when you want a branch-level formatter probe instead of spelling every file path out. -- `format --check` labels whitespace-only formatter output separately from more invasive changes such as include reordering. +- `format --check` labels whitespace-only formatter output separately from other non-whitespace changes. - `format` never targets `SourceLists/z*.cpp`; those files stay audit-only even when you opt into risky formatting. -- `format` skips files that use initializer-list guard comments (`//`) unless you explicitly override that, because clang-format fights this repo-specific convention. +- `format` skips files that use initializer-list guard comments (`//`) unless you explicitly override that, because clang-format fights this repo-specific layout convention. - `clang-format` itself is optional. If it is not on `PATH`, install it locally or point the helper at it with `CLANG_FORMAT=/path/to/clang-format`. - Do not pass `--include-match-sensitive` unless you are deliberately taking on verification work afterwards. diff --git a/AGENTS.md b/AGENTS.md index 95cd388f2..9285959f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,8 +110,8 @@ cleanup rules, including jumbo include spacing, initializer-list comment markers placement, pointer style, and how to keep style work safe in match-sensitive code. Use `python tools/code_style.py audit --base origin/main` before a branch-wide style pass. -It classifies changed files, reports repo-specific findings, and only treats a narrow -allowlisted subset of C/C++ files as clang-format candidates by default. +It classifies changed files, reports repo-specific findings, and only treats the tiny +Frontend/FEng default bucket as clang-format candidates by default. ### decomp-diff.py — Diff & symbol overview diff --git a/README.md b/README.md index d0103a8d1..037a995f2 100644 --- a/README.md +++ b/README.md @@ -287,10 +287,10 @@ If you have `clang-format` installed locally, you can also use: python tools/code_style.py format --check src/Speed/Indep/Src/Frontend/FEManager.cpp ``` -The formatter wrapper only targets a narrow allowlisted subset of C/C++ files by default. That allowlist is about limiting churn, not about whitespace changing codegen by itself. The risky cases are formatter-driven changes such as include reordering and files that rely on the repo's initializer-list guard comments, so match-sensitive code is still skipped unless you explicitly pass `--include-match-sensitive` and verify the affected unit afterwards. +The formatter wrapper only targets a tiny default C/C++ bucket by default: currently `src/Speed/Indep/Src/Frontend/` and `src/Speed/Indep/Src/FEng/`. Nothing magical happens in those directories; they are just the initial low-churn UI-heavy areas chosen for branch-wide formatter probes, while the rest of `src/` stays opt-in because most of this repo is match-sensitive decomp code. `SourceLists/z*.cpp` files remain audit-only and are never formatter targets. -`format --check` now distinguishes whitespace-only formatter deltas from more invasive output such as include reordering. -Files that use the repo's initializer-list guard comments (`//`) are skipped by default because clang-format fights that convention; override only if you are deliberately inspecting that output. +`format --check` now distinguishes whitespace-only formatter deltas from other non-whitespace output changes. +Files that use the repo's initializer-list guard comments (`//`) are skipped by default because clang-format fights that convention by reflowing the guarded initializer layout. That is a source-layout concern, not a claim that whitespace by itself changes codegen; if you override it, verify the affected unit afterwards. For declaration-kind checks, header declarations are treated as the repo source of truth; otherwise the helper falls back to the PS2 dump rule (`public:` / `private:` / `protected:` means `class`, no visibility labels means `struct`). `clang-format` is optional. Recommended installs: diff --git a/tools/code_style.py b/tools/code_style.py index 09be61f20..e3f4fb6ea 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -26,8 +26,9 @@ CPP_EXTS = {".c", ".cc", ".cpp", ".h", ".hh", ".hpp"} HEADER_EXTS = {".h", ".hh", ".hpp"} -# Default clang-format allowlist. This is about limiting churn, not guaranteeing -# byte-match safety. +# Seed default for branch-wide clang-format probes: keep the automatic bucket tiny +# and limited to the UI-heavy Frontend/FEng directories. Broader C/C++ stays +# audit-first and opt-in. DEFAULT_FORMAT_CPP_PREFIXES = ( "src/Speed/Indep/Src/Frontend/", "src/Speed/Indep/Src/FEng/", @@ -925,7 +926,7 @@ def build_parser() -> argparse.ArgumentParser: fmt = subparsers.add_parser( "format", parents=[shared], - help="Run clang-format on the default allowlisted files by default", + help="Run clang-format on the tiny Frontend/FEng default allowlist by default", ) fmt.add_argument( "--category", From effd68f12dea2e950d15850321b1078658308905 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 21:59:39 +0100 Subject: [PATCH 019/372] fix mac build --- tools/project.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/project.py b/tools/project.py index a7847c129..793200414 100644 --- a/tools/project.py +++ b/tools/project.py @@ -755,10 +755,13 @@ def write_cargo_rule(): gnu_as_implicit = None ld_cmd = None ld_implicit = None + # macOS has a very low default soft fd limit (256) which is not enough + # for linking hundreds of objects through wibo/wine. + ld_prefix = "ulimit -n 65536 && " if sys.platform == "darwin" else "" if config.platform == Platform.GC_WII: # NGCLD ngcld = compiler_path / "ngcld.exe" - ld_cmd = f"{wrapper_cmd}{ngcld} $ldflags -o $out @$out.rsp" + ld_cmd = f"{ld_prefix}{wrapper_cmd}{ngcld} $ldflags -o $out @$out.rsp" ld_implicit: List[Optional[Path]] = [ compilers_implicit or ngcld, wrapper_implicit, @@ -776,7 +779,7 @@ def write_cargo_rule(): elif config.platform == Platform.X360: # MSVC linker msvc_link = compiler_path / "link.exe" - ld_cmd = f"{wrapper_cmd}{msvc_link} $ldflags /OUT:$out @$out.rsp" + ld_cmd = f"{ld_prefix}{wrapper_cmd}{msvc_link} $ldflags /OUT:$out @$out.rsp" ld_implicit: List[Optional[Path]] = [ compilers_implicit or msvc_link, wrapper_implicit, @@ -795,7 +798,7 @@ def write_cargo_rule(): else: # GNU linker gnu_ld = binutils / f"mips-linux-gnu-ld{EXE}" - ld_cmd = f"{wrapper_cmd}{gnu_ld} $ldflags -o $out @$out.rsp" + ld_cmd = f"{ld_prefix}{wrapper_cmd}{gnu_ld} $ldflags -o $out @$out.rsp" ld_implicit: List[Optional[Path]] = [ compilers_implicit or gnu_ld, wrapper_implicit, From d46f238f937e474de513dbb606ca0221ac1e165a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 22:36:10 +0100 Subject: [PATCH 020/372] tooling: broaden code_style format defaults Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 8 ++++---- AGENTS.md | 4 ++-- README.md | 4 ++-- tools/code_style.py | 33 ++++++++---------------------- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index d6a13c738..97179bab8 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -33,14 +33,14 @@ python tools/code_style.py audit --base origin/main - `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. - `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. - `audit` groups repeated findings by file so branch-wide output stays readable. -- Use `audit --category safe-cpp` for the tool's intentionally tiny Frontend/FEng default-format bucket and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. -- `format --check` is an opt-in wrapper around the repo's `.clang-format`, but it only targets the tool's default allowlisted C/C++ files by default. +- Use `audit --category safe-cpp` when you want a smaller Frontend/FEng-focused subset and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. +- `format --check` is an opt-in wrapper around the repo's `.clang-format`, and by default it targets eligible changed C/C++ files, including match-sensitive code. - Use `format --check --base origin/main --category safe-cpp` when you want a branch-level formatter probe instead of spelling every file path out. - `format --check` labels whitespace-only formatter output separately from other non-whitespace changes. - `format` never targets `SourceLists/z*.cpp`; those files stay audit-only even when you opt into risky formatting. -- `format` skips files that use initializer-list guard comments (`//`) unless you explicitly override that, because clang-format fights this repo-specific layout convention. +- Files that use initializer-list guard comments (`//`) are still formatter targets; if a formatting pass touches match-sensitive code, verify the affected unit afterwards. - `clang-format` itself is optional. If it is not on `PATH`, install it locally or point the helper at it with `CLANG_FORMAT=/path/to/clang-format`. -- Do not pass `--include-match-sensitive` unless you are deliberately taking on verification work afterwards. +- Do not assume a formatting-only change is automatically byte-stable; verify affected units when the formatter touches match-sensitive code. ## Phase 1: Classify the File Before Cleaning diff --git a/AGENTS.md b/AGENTS.md index 9285959f8..e5f644c7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,8 +110,8 @@ cleanup rules, including jumbo include spacing, initializer-list comment markers placement, pointer style, and how to keep style work safe in match-sensitive code. Use `python tools/code_style.py audit --base origin/main` before a branch-wide style pass. -It classifies changed files, reports repo-specific findings, and only treats the tiny -Frontend/FEng default bucket as clang-format candidates by default. +It classifies changed files, reports repo-specific findings, and can run clang-format +across eligible changed C/C++ files by default (excluding `SourceLists/z*.cpp`). ### decomp-diff.py — Diff & symbol overview diff --git a/README.md b/README.md index eba142b0e..88ecd7210 100644 --- a/README.md +++ b/README.md @@ -277,10 +277,10 @@ If you have `clang-format` installed locally, you can also use: python tools/code_style.py format --check src/Speed/Indep/Src/Frontend/FEManager.cpp ``` -The formatter wrapper only targets a tiny default C/C++ bucket by default: currently `src/Speed/Indep/Src/Frontend/` and `src/Speed/Indep/Src/FEng/`. Nothing magical happens in those directories; they are just the initial low-churn UI-heavy areas chosen for branch-wide formatter probes, while the rest of `src/` stays opt-in because most of this repo is match-sensitive decomp code. +The formatter wrapper targets eligible changed C/C++ files by default, including match-sensitive code. If you want a smaller focused pass, restrict it with `--category safe-cpp`, which currently maps to `src/Speed/Indep/Src/Frontend/` and `src/Speed/Indep/Src/FEng/`. `SourceLists/z*.cpp` files remain audit-only and are never formatter targets. `format --check` now distinguishes whitespace-only formatter deltas from other non-whitespace output changes. -Files that use the repo's initializer-list guard comments (`//`) are skipped by default because clang-format fights that convention by reflowing the guarded initializer layout. That is a source-layout concern, not a claim that whitespace by itself changes codegen; if you override it, verify the affected unit afterwards. +Files that use the repo's initializer-list guard comments (`//`) are formatter targets too. If a formatting pass touches match-sensitive code, rebuild and verify the affected unit afterwards instead of assuming the change is automatically byte-stable. For declaration-kind checks, header declarations are treated as the repo source of truth; otherwise the helper falls back to the PS2 dump rule (`public:` / `private:` / `protected:` means `class`, no visibility labels means `struct`). `clang-format` is optional. Recommended installs: diff --git a/tools/code_style.py b/tools/code_style.py index e3f4fb6ea..ddbe3436d 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -26,10 +26,10 @@ CPP_EXTS = {".c", ".cc", ".cpp", ".h", ".hh", ".hpp"} HEADER_EXTS = {".h", ".hh", ".hpp"} -# Seed default for branch-wide clang-format probes: keep the automatic bucket tiny -# and limited to the UI-heavy Frontend/FEng directories. Broader C/C++ stays -# audit-first and opt-in. -DEFAULT_FORMAT_CPP_PREFIXES = ( +# Small focused C/C++ subset for targeted probes. The format command itself +# now covers all eligible changed C/C++ files by default; this bucket remains +# useful when a caller explicitly wants a narrower Frontend/FEng-only pass. +SAFE_CPP_PREFIXES = ( "src/Speed/Indep/Src/Frontend/", "src/Speed/Indep/Src/FEng/", ) @@ -135,7 +135,7 @@ def path_category(path: str) -> str: return "tooling" if path.startswith(JUMBO_PREFIX): return "jumbo-source-list" - if any(path.startswith(prefix) for prefix in DEFAULT_FORMAT_CPP_PREFIXES): + if any(path.startswith(prefix) for prefix in SAFE_CPP_PREFIXES): return "safe-cpp" if ext in CPP_EXTS else "safe-other" if any(path.startswith(prefix) for prefix in MATCH_SENSITIVE_PREFIXES): return "match-sensitive-cpp" if ext in CPP_EXTS else "match-sensitive-other" @@ -785,9 +785,7 @@ def find_clang_format() -> str: def format_paths(paths: Iterable[str], include_match_sensitive: bool) -> List[str]: - allowed = {"safe-cpp"} - if include_match_sensitive: - allowed.add("match-sensitive-cpp") + allowed = {"safe-cpp", "match-sensitive-cpp"} return [ relpath(path) @@ -808,17 +806,11 @@ def command_format(args: argparse.Namespace) -> int: clang_format = find_clang_format() changed: List[str] = [] changed_summaries: Dict[str, str] = {} - skipped_initializer_guards: List[str] = [] - for path in selected: abs_path = os.path.join(root_dir, path) with open(abs_path, encoding="utf-8", errors="ignore") as f: before = f.read() - if has_initializer_guard_comments(before) and not args.include_initializer_guards: - skipped_initializer_guards.append(path) - continue - if args.check: result = subprocess.run( [clang_format, "--style=file", abs_path], @@ -837,13 +829,6 @@ def command_format(args: argparse.Namespace) -> int: return result.returncode changed.append(path) - if skipped_initializer_guards: - print("Skipped files with initializer-list guard comments:") - for path in skipped_initializer_guards: - print(f" {path}") - print(" clang-format fights this repo convention; inspect these manually or override explicitly.") - print() - if args.check: if changed: print("Would reformat:") @@ -926,7 +911,7 @@ def build_parser() -> argparse.ArgumentParser: fmt = subparsers.add_parser( "format", parents=[shared], - help="Run clang-format on the tiny Frontend/FEng default allowlist by default", + help="Run clang-format on changed C/C++ files by default (SourceLists stay excluded)", ) fmt.add_argument( "--category", @@ -942,12 +927,12 @@ def build_parser() -> argparse.ArgumentParser: fmt.add_argument( "--include-match-sensitive", action="store_true", - help="Also format match-sensitive C/C++ files (dangerous; verify afterwards). SourceLists files stay excluded.", + help="Deprecated no-op kept for compatibility; eligible match-sensitive C/C++ files are already included by default.", ) fmt.add_argument( "--include-initializer-guards", action="store_true", - help="Also format files that use initializer-list guard comments (`//`). Disabled by default because clang-format fights that repo convention.", + help="Deprecated no-op kept for compatibility; files with initializer-list guard comments are formatted by default.", ) fmt.set_defaults(func=command_format) From 1bb0e62e8e2ec3b4e0d19de981f65a43728bc0a6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 13 Mar 2026 23:22:58 +0100 Subject: [PATCH 021/372] tooling: globalize clang-format scope Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 3 ++- AGENTS.md | 2 +- README.md | 2 +- tools/code_style.py | 14 +++++++------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 97179bab8..5218fb40b 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -35,9 +35,10 @@ python tools/code_style.py audit --base origin/main - `audit` groups repeated findings by file so branch-wide output stays readable. - Use `audit --category safe-cpp` when you want a smaller Frontend/FEng-focused subset and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. - `format --check` is an opt-in wrapper around the repo's `.clang-format`, and by default it targets eligible changed C/C++ files, including match-sensitive code. +- Use `format --check --base origin/main` for a branch-wide formatter pass over all changed C/C++ files. - Use `format --check --base origin/main --category safe-cpp` when you want a branch-level formatter probe instead of spelling every file path out. - `format --check` labels whitespace-only formatter output separately from other non-whitespace changes. -- `format` never targets `SourceLists/z*.cpp`; those files stay audit-only even when you opt into risky formatting. +- `format` also accepts `SourceLists/z*.cpp` and other repo C/C++ files; if a formatting pass touches match-sensitive code, verify the affected unit afterwards. - Files that use initializer-list guard comments (`//`) are still formatter targets; if a formatting pass touches match-sensitive code, verify the affected unit afterwards. - `clang-format` itself is optional. If it is not on `PATH`, install it locally or point the helper at it with `CLANG_FORMAT=/path/to/clang-format`. - Do not assume a formatting-only change is automatically byte-stable; verify affected units when the formatter touches match-sensitive code. diff --git a/AGENTS.md b/AGENTS.md index e5f644c7b..d367fc237 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -111,7 +111,7 @@ placement, pointer style, and how to keep style work safe in match-sensitive cod Use `python tools/code_style.py audit --base origin/main` before a branch-wide style pass. It classifies changed files, reports repo-specific findings, and can run clang-format -across eligible changed C/C++ files by default (excluding `SourceLists/z*.cpp`). +across eligible changed C/C++ files by default. ### decomp-diff.py — Diff & symbol overview diff --git a/README.md b/README.md index 88ecd7210..b67d7d54c 100644 --- a/README.md +++ b/README.md @@ -274,11 +274,11 @@ python tools/code_style.py format --check --base origin/main --category safe-cpp If you have `clang-format` installed locally, you can also use: ```sh +python tools/code_style.py format --check --base origin/main python tools/code_style.py format --check src/Speed/Indep/Src/Frontend/FEManager.cpp ``` The formatter wrapper targets eligible changed C/C++ files by default, including match-sensitive code. If you want a smaller focused pass, restrict it with `--category safe-cpp`, which currently maps to `src/Speed/Indep/Src/Frontend/` and `src/Speed/Indep/Src/FEng/`. -`SourceLists/z*.cpp` files remain audit-only and are never formatter targets. `format --check` now distinguishes whitespace-only formatter deltas from other non-whitespace output changes. Files that use the repo's initializer-list guard comments (`//`) are formatter targets too. If a formatting pass touches match-sensitive code, rebuild and verify the affected unit afterwards instead of assuming the change is automatically byte-stable. For declaration-kind checks, header declarations are treated as the repo source of truth; otherwise the helper falls back to the PS2 dump rule (`public:` / `private:` / `protected:` means `class`, no visibility labels means `struct`). diff --git a/tools/code_style.py b/tools/code_style.py index ddbe3436d..ecb85f713 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -695,14 +695,14 @@ def command_audit(args: argparse.Namespace) -> int: print(f" {category}: {len(by_category[category])}") print() - default_format_candidates = [ + safe_cpp_candidates = [ path for path in paths if path_category(path) == "safe-cpp" and os.path.splitext(path)[1] in CPP_EXTS ] - if default_format_candidates: - print("Default clang-format candidates:") - for path in default_format_candidates: + if safe_cpp_candidates: + print("Focused safe-cpp subset:") + for path in safe_cpp_candidates: print(f" {path}") print() @@ -785,12 +785,12 @@ def find_clang_format() -> str: def format_paths(paths: Iterable[str], include_match_sensitive: bool) -> List[str]: - allowed = {"safe-cpp", "match-sensitive-cpp"} + del include_match_sensitive return [ relpath(path) for path in paths - if path_category(path) in allowed and os.path.splitext(path)[1] in CPP_EXTS + if os.path.splitext(path)[1] in CPP_EXTS ] @@ -911,7 +911,7 @@ def build_parser() -> argparse.ArgumentParser: fmt = subparsers.add_parser( "format", parents=[shared], - help="Run clang-format on changed C/C++ files by default (SourceLists stay excluded)", + help="Run clang-format on changed C/C++ files by default", ) fmt.add_argument( "--category", From 2564a6ac7a92aebf7438551f03141ea47cd5df40 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 17 Mar 2026 11:48:31 +0100 Subject: [PATCH 022/372] agent % --- AGENTS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d367fc237..fc4f7af24 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -363,13 +363,13 @@ python tools/decomp-status.py --unit main/Path/To/TU Commit whenever the match percentage increases (e.g. you matched a new function). Use this format for the commit message: ``` -n.n%: short description of what was matched or changed +n.n[n]%: short description of what was matched or changed ``` Examples: - `42.1%: match UpdateCamera` -- `78.5%: match PlayerController constructor and destructor` +- `78.56%: match PlayerController constructor and destructor` - `100.0%: full match for zAnim` Do not batch up multiple percentage milestones into one commit — commit as each improvement lands. From 80df004796b6ca9b25d64be548945ab9b4377cfb Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 18 Mar 2026 00:56:02 +0100 Subject: [PATCH 023/372] fix: link shared assets and track agent skills symlinks --- .agents/skills/code_style | 1 + .agents/skills/execute | 1 + .agents/skills/ghidra | 1 + .agents/skills/implement | 1 + .agents/skills/line_lookup | 1 + .agents/skills/lookup | 1 + .agents/skills/refiner | 1 + .agents/skills/scaffold | 1 + 8 files changed, 8 insertions(+) create mode 120000 .agents/skills/code_style create mode 120000 .agents/skills/execute create mode 120000 .agents/skills/ghidra create mode 120000 .agents/skills/implement create mode 120000 .agents/skills/line_lookup create mode 120000 .agents/skills/lookup create mode 120000 .agents/skills/refiner create mode 120000 .agents/skills/scaffold diff --git a/.agents/skills/code_style b/.agents/skills/code_style new file mode 120000 index 000000000..edb444fb7 --- /dev/null +++ b/.agents/skills/code_style @@ -0,0 +1 @@ +../../.github/skills/code_style \ No newline at end of file diff --git a/.agents/skills/execute b/.agents/skills/execute new file mode 120000 index 000000000..ff61f8ce2 --- /dev/null +++ b/.agents/skills/execute @@ -0,0 +1 @@ +../../.github/skills/execute \ No newline at end of file diff --git a/.agents/skills/ghidra b/.agents/skills/ghidra new file mode 120000 index 000000000..e348149ed --- /dev/null +++ b/.agents/skills/ghidra @@ -0,0 +1 @@ +../../.github/skills/ghidra \ No newline at end of file diff --git a/.agents/skills/implement b/.agents/skills/implement new file mode 120000 index 000000000..609ac0075 --- /dev/null +++ b/.agents/skills/implement @@ -0,0 +1 @@ +../../.github/skills/implement \ No newline at end of file diff --git a/.agents/skills/line_lookup b/.agents/skills/line_lookup new file mode 120000 index 000000000..98bcd185e --- /dev/null +++ b/.agents/skills/line_lookup @@ -0,0 +1 @@ +../../.github/skills/line_lookup \ No newline at end of file diff --git a/.agents/skills/lookup b/.agents/skills/lookup new file mode 120000 index 000000000..e0466a56a --- /dev/null +++ b/.agents/skills/lookup @@ -0,0 +1 @@ +../../.github/skills/lookup \ No newline at end of file diff --git a/.agents/skills/refiner b/.agents/skills/refiner new file mode 120000 index 000000000..d61e56ea1 --- /dev/null +++ b/.agents/skills/refiner @@ -0,0 +1 @@ +../../.github/skills/refiner \ No newline at end of file diff --git a/.agents/skills/scaffold b/.agents/skills/scaffold new file mode 120000 index 000000000..5684eddc2 --- /dev/null +++ b/.agents/skills/scaffold @@ -0,0 +1 @@ +../../.github/skills/scaffold \ No newline at end of file From d76c2f640a7b741902a5dbe13adf6f5423e89e46 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 18 Mar 2026 17:35:17 +0100 Subject: [PATCH 024/372] fix diff --- tools/decomp-diff.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/decomp-diff.py b/tools/decomp-diff.py index 13e54e26f..7b50a6b02 100644 --- a/tools/decomp-diff.py +++ b/tools/decomp-diff.py @@ -20,6 +20,7 @@ from typing import Any, Dict, List, Optional, Tuple from _common import ( ROOT_DIR, + RELOC_DIFF_CHOICES, ToolError, build_objdiff_symbol_rows, fail, From 1a54bf300cac21f922f60417ef1928a37f49e777 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 19 Mar 2026 09:03:54 +0100 Subject: [PATCH 025/372] improvements derived from zBWare review changes --- .github/skills/code_style/SKILL.md | 33 +++++++++- .github/skills/execute/SKILL.md | 9 +++ .github/skills/implement/SKILL.md | 29 +++++++++ .github/skills/refiner/SKILL.md | 19 ++++++ .github/skills/scaffold/SKILL.md | 20 +++++- tools/code_style.py | 63 +++++++++++++++++- tools/decomp-workflow.py | 100 +++++++++++++++++++++++------ 7 files changed, 251 insertions(+), 22 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 5218fb40b..d5b2cbbb3 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -31,7 +31,7 @@ python tools/code_style.py audit --base origin/main - `audit` also checks touched `class` / `struct` declarations against known header declarations and, when no header exists, against the PS2 visibility rule. - `audit` warns on touched local forward declarations when the repo already has a header for that type. - `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. -- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. +- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, bare `#if MACRO` presence checks, recovered layout members that still use raw `unsigned char` / `unsigned short`, and missing or misordered `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's prologue is touched. - `audit` groups repeated findings by file so branch-wide output stays readable. - Use `audit --category safe-cpp` when you want a smaller Frontend/FEng-focused subset and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. - `format --check` is an opt-in wrapper around the repo's `.clang-format`, and by default it targets eligible changed C/C++ files, including match-sensitive code. @@ -95,7 +95,14 @@ Foo::Foo() - Use `nullptr` exclusively for null pointers. - Prefer `if (ptr)` / `if (!ptr)` over explicit null comparisons when the change is local and verified safe. - When a match-sensitive TU has many explicit `nullptr` checks and you decide to normalize them, prefer one mechanical full-TU pass over piecemeal cleanup. Rebuild the unit and re-check its status before keeping the rewrite. +- When a helper is doing address arithmetic, prefer `intptr_t` / `uintptr_t` or byte-pointer (`reinterpret_cast`) math over plain `int` parameters or integerized pointer subtraction. - Inline assembly is acceptable when it is needed to preserve dead-code compares, ordering, or other compiler behavior that source alone cannot reproduce. +- In low-level list / node / allocator code, prefer existing helper methods such as `AddBefore`, `AddAfter`, `Remove`, `GetPrev`, `GetNext`, or typed accessors over open-coding link rewiring once the helper exists. + +### Header prologues and preprocessor checks + +- In headers, keep the guard / `EA_PRAGMA_ONCE_SUPPORTED` block before any project `#include`; do not place includes ahead of `#pragma once`. +- Use `#ifdef MACRO` / `#ifndef MACRO` for presence checks. Reserve bare `#if MACRO` for cases where you really need the macro's numeric value. ### Forward declarations and local prototypes @@ -103,6 +110,7 @@ Foo::Foo() - If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. - If the repo only has an empty or stub owner header, and line info / surrounding source clearly points at that header's subsystem, prefer populating that owner header over leaving a recovered project type declaration inside a `.cpp`. - Only keep a local forward declaration when no canonical repo header exists yet and you have verified that the ownership is still unresolved. +- Likewise for project free functions: if a declaration is shared across translation units, move it into the owning header instead of leaving ad-hoc local prototypes in `.cpp` files. - Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. ### Pointer style @@ -117,10 +125,13 @@ Foo::Foo() - Preserve the original `class` / `struct` kind from existing headers or Dwarf / PS2 evidence; do not treat it as a cosmetic style choice. - Treat header declarations as the repo source of truth. If the repo only has local `.cpp` partial declarations, verify the kind with the PS2 dump instead of copying them blindly. - Even forward declarations and local partial declarations should use the accurate keyword when known. +- Keep the `// total size: 0x...` comment above the recovered type declaration instead of burying it inside the body. +- When a recovered type is a `class`, keep explicit access sections and put the method/accessor block before the member layout block unless existing repo evidence shows otherwise. - Preserve the member naming style that DWARF shows. Some types use `mMember`, others use `m_member`; do not normalize them. - Preserve recovered member names, types, order, and offset comments. Do not invent placeholder members named `pad`, `unk`, `unknown`, or `field_XXXX` for game code just to make a layout compile. - If a member is genuinely unknown, stop and verify it with `find-symbol.py`, GC Dwarf, and PS2 data. If the layout is still incomplete, add a short TODO above the type instead of burying uncertainty in fake member names. - Add offset / size comments when you are writing recovered type layouts from DWARF. +- In recovered layouts, prefer explicit-width aliases such as `uint8` / `uint16` when the field width is known. Use plain `char` for text / byte buffers and `signed char` when the field is a signed 8-bit counter. - Define inline member functions in headers only when DWARF shows that they are genuinely inlined in the binary. - Use `struct` for POD-like data carriers with public fields; use `class` for behavior-heavy types only when that matches the recovered type information. - Keep tiny placeholder methods as concise inline bodies when that is already the local pattern. @@ -134,13 +145,27 @@ Foo::Foo() ### Dense local code - Expand dense one-line helper structs, declaration blocks, and function bodies in non-match-sensitive files into normal multiline formatting. +- In low-level headers, prefer normal multi-line bodies for touched inline operators and accessors instead of stacking `{ return ...; }` on one line, unless the surrounding file clearly uses intentional placeholder one-liners. - Prefer readable blocks over stacked one-line statements when behavior does not depend on exact source shape. +- In touched validation/parsing code, prefer explicit min/max or boundary checks over equivalent magic-constant arithmetic when the clearer form still compiles to the verified result. +- In parser/state-table code, prefer named enums and enum-typed state variables over anonymous integer state codes when that rewrite is verified safe. + +### Recovery markers + +- Remove stale recovery markers such as `// TODO`, `// UNSOLVED`, or `// STRIPPED` when the touched code is now implemented or understood. +- If a marker still needs to stay, give it short context such as ownership uncertainty, a Dwarf caveat, a platform/config note, or a scratch/link reference. Avoid bare marker-only comments. +- Do not leave `// TODO` hanging off a declaration or helper you just implemented; either finish the thought or remove the marker. ### Uncertain ownership - If a declaration or global clearly compiles but its original home is uncertain, add a short TODO comment instead of inventing structure you cannot justify yet. - When ownership matters, verify it with `decomp-workflow.py`, `decomp-context.py`, and `line-lookup` before moving code. +### Readable helper extraction + +- When touched recovered code repeats the same pointer/boundary arithmetic, prefer a short named helper or accessor such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, `GetStringTableStart`, or `GetStringTableEnd` if that shape is already supported by Dwarf/inlining evidence. +- Prefer call sites that use those helpers or existing container APIs over re-encoding the same arithmetic or link manipulation inline. + ## Phase 3: Things Not To "Clean Up" Blindly - Do not move an inline method out of a header just because it looks cleaner. @@ -172,3 +197,9 @@ Keep the cleanup only if the build succeeds and the relevant match status is unc - The trailing `//` initializer-list markers are an intentional repo convention, not noise to remove. - Small `if (ptr)` cleanup batches can be kept in match-sensitive code, but only after rebuilding the affected unit. - Dense frontend shim files benefit from multiline struct/prototype/function formatting. +- Header prologues should keep the `EA_PRAGMA_ONCE_SUPPORTED` block ahead of includes, not after them. +- Bare `#if MACRO` presence checks are review bait; use `#ifdef` / `#ifndef` unless you are intentionally testing a numeric config value. +- Reviewed recovered headers tend to keep total-size comments above the type, methods before fields, explicit access sections, and fixed-width aliases for width-known narrow integer members. +- Reviewed fixups also remove stale bare recovery markers or replace them with context, and prefer existing list/node helpers over hand-written pointer/link rewiring. +- Some reviewed fixups improved readability without losing match by replacing opaque range-check arithmetic with explicit bounds and by moving repeated pointer/boundary math behind short named helpers. +- Other recurring review churn came from plain-`int` address helpers, stray local `.cpp` prototypes for shared functions, and integer-coded parser states where named enums were clearer but still matched. diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 7abf9cb3f..5ba0bd156 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -11,6 +11,9 @@ the produced C++ compiles to byte-identical object code against the original ret For each function, "done" means both objdiff and normalized DWARF are exact. +Human review is not a substitute for running `dwarf compare`. Each function should hit +its own `verify` gate before you treat it as ready to hand off, commit, or move past. + ## Overview This workflow combines several smaller workflows: @@ -152,6 +155,10 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If it fails, follow up with `decomp-workflow.py diff` and `decomp-workflow.py dwarf` until both checks pass. +Do not queue up several "probably done" functions and leave the DWARF check for later. +Close the `verify` gate per function before moving on whenever feasible; otherwise the +reviewer ends up doing avoidable DWARF triage. + ### 3g. Periodic reassessment After every few functions, re-run the full status check: @@ -189,6 +196,8 @@ For any remaining nonmatching functions, make one final pass using the implement or refiner workflow with all context accumulated during the session. Do not report a function as complete unless its per-function `verify` check also passes. +Do not hand a function to review as "done except maybe DWARF" — either resolve the DWARF +failure yourself or explicitly call out the blocker and why it remains. ## Phase 5: Report diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 25f56b926..cbd94da78 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -9,6 +9,11 @@ Your goal is to decompile a specific function: writing C++ source that compiles A function is not done until it is exact in both objdiff and normalized DWARF. +Reviewers should not be spending their time rediscovering DWARF mismatches. Before you +report progress, ask for review, hand the function off, or switch to another target, you +must run the per-function verification gate yourself and treat any DWARF failure as your +next task, not as review debt. + ## Phase 1: Gather Context Collect data from **all** of these sources in parallel where possible. @@ -156,6 +161,16 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If the build fails, fix compilation errors first. +As soon as you have a compiling draft, run the combined verification gate immediately: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +Do this before you spend a long time polishing late instruction mismatches. If `verify` +already shows a DWARF failure, fix that first so you are not polishing code the reviewer +will bounce anyway. + ### Check the diff ```sh @@ -203,6 +218,17 @@ debug-line owner files for each DWARF `// Range:` block, which makes it much eas spot inlines that are coming from the wrong header or owner file. Exact line-number agreement is a useful secondary hint, but file ownership is the first thing to check. +Use this as the default loop when the function compiles but `verify` is still failing: + +1. Run `verify`. +2. If DWARF fails, run `dwarf`. +3. Fix the structural issue the DWARF diff is pointing at first: missing/extra locals, + wrong qualifiers or parameter types, wrong inline ownership, wrong helper/header owner, + or a source shape that outlined something that should be inlined. +4. Rebuild and rerun `verify`. +5. Only return to instruction-by-instruction cleanup once the remaining failures are no + longer obvious DWARF-compare issues. + Manual fallback: After writing your code, you can also run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing @@ -233,6 +259,9 @@ Every mismatched instruction is a signal — don't settle for "close enough". Reaching 100% instruction matching status is not enough. Stay in the loop until `verify` passes, which means the DWARF of the function also matches after normalization. +Do not leave a function in a "review-ready" or "good enough for now" state with a known +DWARF failure unless you are explicitly blocked and you document that blocker clearly. + ## Phase 5: Report Summarize: diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index a6aeb2125..0054e6e18 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -15,9 +15,25 @@ approaches that were tried before — instead, apply systematic lateral analysis - A diff is available (`decomp-diff.py -u -d `). - The "obvious" translation from Ghidra has been attempted. - You have been given the current source code and the diff. +- You have already run the per-function `verify` gate and know whether the remaining work + is still structural DWARF cleanup or true late-stage instruction cleanup. + +Refiner is not the place to dump unresolved DWARF debt on a reviewer. If `verify` or +`dwarf` is still showing obvious structural mismatches (missing locals, wrong types, +wrong inline ownership, wrong helper/header owner), fix those first or drop back to the +implementer workflow before doing late instruction polish. ## Phase 1: Read the full diff without collapsing +Before you start a refiner pass, confirm the gate status: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +If the combined gate is failing for reasons that are still clearly visible in the DWARF +diff, address those first instead of treating them as reviewer follow-up. + Preferred shortcut: ```sh @@ -151,6 +167,9 @@ DWARF mismatches to watch for: - Wrong return type - Missing inlined function records (means an inline call was outlined) +If these mismatches are still present, you are not in pure refiner territory yet. Resolve +them before you ask a reviewer to spend time on the function. + ## Phase 4: Report Summarize: diff --git a/.github/skills/scaffold/SKILL.md b/.github/skills/scaffold/SKILL.md index a2f062f50..3fe3d9256 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -39,6 +39,9 @@ Preserve the real `class` / `struct` kind while scaffolding. Check existing head then use Dwarf plus PS2 visibility / vtable info to decide the type kind. Even temporary forward declarations should match the known original kind. +Keep the header prologue in repo order: header guard, `EA_PRAGMA_ONCE_SUPPORTED` block, +then includes. Do not drop project includes ahead of `#pragma once`. + If the repo already has a header for a type you need, include that header instead of adding a new local forward declaration. Only forward-declare when no canonical repo header exists yet and you have verified that the ownership is still unresolved. @@ -47,11 +50,26 @@ Preserve real member names, types, order, and offset comments while scaffolding. fill gaps with invented `pad`, `unk`, or `field_XXXX` members for game types; verify the layout from Dwarf / PS2 data and leave a TODO over the type if a field is still uncertain. +Keep the `// total size: 0x...` comment above the recovered type declaration. When the +recovered type is a `class`, keep explicit access sections and prefer putting methods / +accessors before the member layout block unless existing repo evidence says otherwise. + +When a recovered field width is known, prefer explicit-width aliases such as `uint8` / +`uint16` over raw `unsigned char` / `unsigned short`. Use plain `char` for string or byte +buffers and `signed char` when the field is a signed 8-bit counter. + +If a recovered type repeatedly walks neighbors, boundaries, or in-object offsets, prefer +small named helpers such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, or boundary getters +instead of repeating raw pointer arithmetic at each call site. + +When those helpers operate on addresses or byte offsets, prefer `intptr_t` / `uintptr_t` +or explicit byte-pointer arithmetic instead of plain `int` address parameters. + Only create headers if it's really necessary (the struct doesn't have inlines so you can't determine in which header file it goes and it's thematically very different from the other structs that use it), otherwise put it into the one you determined to be correct. The dwarf often has duplicated inlines, clean those up according to the order in the PS2 info. -Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header. +Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header, and say why (ownership uncertainty, circular dependency, dwarf caveat, etc.) instead of leaving a bare marker. ## Phase 3: Add needed files to jumbo file and compile diff --git a/tools/code_style.py b/tools/code_style.py index ecb85f713..61c3f2ee3 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -84,6 +84,7 @@ class Finding: ) USING_NAMESPACE_PATTERN = re.compile(r"^\s*using\s+namespace\b") NULL_PATTERN = re.compile(r"\bNULL\b") +BARE_PRESENCE_IF_PATTERN = re.compile(r"^\s*#if\s+([A-Za-z_][A-Za-z0-9_]*)\s*$") HEADER_GUARD_IFNDEF_PATTERN = re.compile(r"^\s*#ifndef\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) HEADER_GUARD_DEFINE_PATTERN = re.compile(r"^\s*#define\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) EA_PRAGMA_BLOCK_PATTERN = re.compile( @@ -92,6 +93,16 @@ class Finding: r".*?^\s*#endif\s*$", re.MULTILINE | re.DOTALL, ) +EA_PRAGMA_IFDEF_PATTERN = re.compile( + r"^\s*#ifdef\s+EA_PRAGMA_ONCE_SUPPORTED\s*$", re.MULTILINE +) +RECOVERED_LAYOUT_COMMENT_PATTERN = re.compile( + r"//\s*offset 0x[0-9A-Fa-f]+,\s*size 0x[0-9A-Fa-f]+" +) +RECOVERED_NARROW_UNSIGNED_PATTERN = re.compile(r"\bunsigned\s+(char|short)\b") +BARE_RECOVERY_MARKER_PATTERN = re.compile( + r"//\s*(TODO|UNSOLVED|STRIPPED)\b(?:\s*[.:,-]*)?\s*$" +) SUSPICIOUS_MEMBER_PATTERN = re.compile( r"^(?:" r"_?pad(?:ding)?[0-9A-Fa-f_]*" @@ -441,6 +452,16 @@ def audit_style_guide_rules( if touched_lines is not None and idx not in touched_lines: continue stripped = line.strip() + bare_recovery_marker_match = BARE_RECOVERY_MARKER_PATTERN.search(line) + if bare_recovery_marker_match is not None: + findings.append( + Finding( + path, + idx, + "INFO", + f"`// {bare_recovery_marker_match.group(1)}` has no context; add a short reason or remove the stale recovery marker", + ) + ) if stripped.startswith("//"): continue @@ -471,9 +492,35 @@ def audit_style_guide_rules( "use `nullptr` instead of `NULL`", ) ) + bare_presence_if_match = BARE_PRESENCE_IF_PATTERN.match(line) + if bare_presence_if_match is not None: + findings.append( + Finding( + path, + idx, + "WARN", + f"bare `#if {bare_presence_if_match.group(1)}` looks like a presence check; prefer `#ifdef {bare_presence_if_match.group(1)}` unless a numeric test is intentional", + ) + ) + narrow_type_match = RECOVERED_NARROW_UNSIGNED_PATTERN.search(line) + if ( + narrow_type_match is not None + and RECOVERED_LAYOUT_COMMENT_PATTERN.search(line) is not None + ): + preferred = "uint8" if narrow_type_match.group(1) == "char" else "uint16" + findings.append( + Finding( + path, + idx, + "INFO", + f"recovered layout member uses `{narrow_type_match.group(0)}`; prefer explicit-width `{preferred}` when the field width is known", + ) + ) if ext in HEADER_EXTS: - should_check_guard = touched_lines is None or any(line_no <= 8 for line_no in touched_lines) + should_check_guard = touched_lines is None or any( + line_no <= 12 for line_no in touched_lines + ) if should_check_guard: has_ifndef = HEADER_GUARD_IFNDEF_PATTERN.search(text) is not None has_define = HEADER_GUARD_DEFINE_PATTERN.search(text) is not None @@ -487,6 +534,20 @@ def audit_style_guide_rules( "header guard should use `#ifndef` / `#define` plus the `EA_PRAGMA_ONCE_SUPPORTED` `#pragma once` block", ) ) + pragma_ifdef_match = EA_PRAGMA_IFDEF_PATTERN.search(text) + if pragma_ifdef_match is not None: + pragma_ifdef_line = text[: pragma_ifdef_match.start()].count("\n") + 1 + for idx, line in enumerate(text.splitlines(), 1): + if line.strip().startswith("#include ") and idx < pragma_ifdef_line: + findings.append( + Finding( + path, + idx, + "WARN", + "header include appears before the `EA_PRAGMA_ONCE_SUPPORTED` block; keep the guard / pragma block ahead of includes", + ) + ) + break return findings diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index 84f2f83d5..6be2b3d80 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -292,6 +292,14 @@ def choose_objdiff_row(unit_name: str, function_name: str, reloc_diffs: str = "n return matches[0] +def resolve_exact_function_name( + unit_name: str, function_name: str, reloc_diffs: str = "none" +) -> str: + return str( + choose_objdiff_row(unit_name, function_name, reloc_diffs=reloc_diffs)["name"] + ) + + def load_dwarf_report( unit_name: str, function_name: str, @@ -642,6 +650,9 @@ def command_function(args: argparse.Namespace) -> None: ensure_decomp_prereqs() print_section(f"Function Workflow: {args.function}") ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name( + args.unit, args.function, reloc_diffs=args.reloc_diffs + ) cmd = python_tool("decomp-context.py", "-u", args.unit, "-f", args.function) if args.no_source: cmd.append("--no-source") @@ -661,9 +672,14 @@ def command_function(args: argparse.Namespace) -> None: print(flush=True) print( "Required completion check: python tools/decomp-workflow.py verify " - f"-u {shlex.quote(args.unit)} -f {shlex.quote(args.function)}", + f"-u {shlex.quote(args.unit)} -f {shlex.quote(resolved_function_name)}", flush=True, ) + if resolved_function_name != args.function: + print( + f"(Resolved exact function name for DWARF-safe follow-up: {resolved_function_name})", + flush=True, + ) def command_unit(args: argparse.Namespace) -> None: @@ -810,8 +826,11 @@ def command_dwarf(args: argparse.Namespace) -> None: print_section(f"DWARF Workflow: {args.unit} / {args.function}") if not args.rebuilt_dwarf_file: ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name(args.unit, args.function) - cmd: List[str] = python_tool("dwarf-compare.py", "-u", args.unit, "-f", args.function) + cmd: List[str] = python_tool( + "dwarf-compare.py", "-u", args.unit, "-f", resolved_function_name + ) if args.summary: cmd.append("--summary") if args.json: @@ -833,18 +852,24 @@ def command_verify(args: argparse.Namespace) -> None: ensure_shared_unit_output(args.unit) objdiff_row = choose_objdiff_row(args.unit, args.function, reloc_diffs=args.reloc_diffs) - dwarf_report = load_dwarf_report( - args.unit, - args.function, - rebuilt_dwarf_file=args.rebuilt_dwarf_file, - ) + resolved_function_name = str(objdiff_row["name"]) + dwarf_load_error: Optional[str] = None + dwarf_report: Optional[Dict[str, Any]] = None + try: + dwarf_report = load_dwarf_report( + args.unit, + resolved_function_name, + rebuilt_dwarf_file=args.rebuilt_dwarf_file, + ) + except WorkflowError as e: + dwarf_load_error = str(e) objdiff_exact = ( objdiff_row["status"] == "match" and objdiff_row["match_percent"] is not None and float(objdiff_row["match_percent"]) >= 100.0 ) - dwarf_exact = bool(dwarf_report["normalized_exact_match"]) + dwarf_exact = bool(dwarf_report["normalized_exact_match"]) if dwarf_report else False overall_ok = objdiff_exact and dwarf_exact objdiff_percent = ( @@ -852,34 +877,56 @@ def command_verify(args: argparse.Namespace) -> None: if objdiff_row["match_percent"] is not None else "-" ) - dwarf_percent = f"{float(dwarf_report['match_percent']):.1f}%" + dwarf_percent = ( + f"{float(dwarf_report['match_percent']):.1f}%" if dwarf_report else "-" + ) print( f"objdiff: {'PASS' if objdiff_exact else 'FAIL'} | " f"{objdiff_percent} | status={objdiff_row['status']} | " f"unmatched~{objdiff_row['unmatched_bytes_est']}B" ) - print( - f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " - f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " - f"change groups={dwarf_report['changed_groups']}" - ) + if dwarf_report: + print( + f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " + f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " + f"change groups={dwarf_report['changed_groups']}" + ) + else: + print("DWARF: FAIL | unable to compare rebuilt vs original DWARF", flush=True) + if resolved_function_name != args.function: + print(f"Resolved DWARF symbol: {resolved_function_name}") print(f"Overall: {'PASS' if overall_ok else 'FAIL'}") print("Done means both objdiff and normalized DWARF are exact for the function.") if overall_ok: return + if dwarf_load_error: + print(flush=True) + print("DWARF compare could not complete:", flush=True) + print(dwarf_load_error, flush=True) + if ( + objdiff_row["status"] == "missing" + and "rebuilt DWARF: function" in dwarf_load_error + and "not found" in dwarf_load_error + ): + print( + "Hint: the rebuilt object does not contain this function yet. " + "Implement the function or fix its ownership/signature first, then rerun verify.", + flush=True, + ) + print(flush=True) print("Follow-up commands:", flush=True) print( f" python tools/decomp-workflow.py diff -u {shlex.quote(args.unit)} " - f"-d {shlex.quote(args.function)}", + f"-d {shlex.quote(resolved_function_name)}", flush=True, ) print( f" python tools/decomp-workflow.py dwarf -u {shlex.quote(args.unit)} " - f"-f {shlex.quote(args.function)}", + f"-f {shlex.quote(resolved_function_name)}", flush=True, ) raise WorkflowError( @@ -941,7 +988,12 @@ def build_parser() -> argparse.ArgumentParser: help="Run decomp-context.py for one function", ) function.add_argument("-u", "--unit", required=True, help="Translation unit name") - function.add_argument("-f", "--function", required=True, help="Function name to inspect") + function.add_argument( + "-f", + "--function", + required=True, + help="Function name to inspect (full name or a unique substring)", + ) function.add_argument( "--no-source", action="store_true", @@ -1086,7 +1138,12 @@ def build_parser() -> argparse.ArgumentParser: help="Compare original vs rebuilt DWARF for one function", ) dwarf.add_argument("-u", "--unit", required=True, help="Translation unit name") - dwarf.add_argument("-f", "--function", required=True, help="Function name to compare") + dwarf.add_argument( + "-f", + "--function", + required=True, + help="Function name to compare (full name or a unique substring)", + ) dwarf.add_argument( "--summary", action="store_true", @@ -1127,7 +1184,12 @@ def build_parser() -> argparse.ArgumentParser: help="Fail unless one function matches in both objdiff and DWARF", ) verify.add_argument("-u", "--unit", required=True, help="Translation unit name") - verify.add_argument("-f", "--function", required=True, help="Function name to verify") + verify.add_argument( + "-f", + "--function", + required=True, + help="Function name to verify (full name or a unique substring)", + ) verify.add_argument( "--reloc-diffs", choices=RELOC_DIFF_CHOICES, From 1b8ec13c1bf0453e18f17d12875cc9b1cd599d24 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 19 Mar 2026 09:51:52 +0100 Subject: [PATCH 026/372] symlink --- .claude | 1 + 1 file changed, 1 insertion(+) create mode 120000 .claude diff --git a/.claude b/.claude new file mode 120000 index 000000000..c0ca46856 --- /dev/null +++ b/.claude @@ -0,0 +1 @@ +.agents \ No newline at end of file From 0bab6b8da55355215734602b5ec9057edf02f82f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 20 Mar 2026 16:22:06 +0100 Subject: [PATCH 027/372] tooling: bootstrap PS2 and Xbox assets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/share_worktree_assets.py | 117 +++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 5 deletions(-) diff --git a/tools/share_worktree_assets.py b/tools/share_worktree_assets.py index 374d168e5..4bfdcf055 100644 --- a/tools/share_worktree_assets.py +++ b/tools/share_worktree_assets.py @@ -13,6 +13,8 @@ python tools/share_worktree_assets.py status --all python tools/share_worktree_assets.py link --all python tools/share_worktree_assets.py bootstrap + python tools/share_worktree_assets.py bootstrap --version EUROPEGERMILESTONE --xbox-xex /path/to/NfsMWEuropeGerMilestone.xex + python tools/share_worktree_assets.py bootstrap --version SLES-53558-A124 --ps2-toolchain-zip /path/to/PS2.zip """ import argparse @@ -21,6 +23,7 @@ import shutil import subprocess import sys +import zipfile from dataclasses import dataclass from typing import Dict, Iterable, List, Optional, Set @@ -39,11 +42,16 @@ class AssetSpec: FIXED_ASSETS = ( AssetSpec(os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "file"), + AssetSpec( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "file", + ), AssetSpec(os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "file"), AssetSpec(os.path.join("orig", "SLES-53558-A124", "NFS.MAP"), "file"), AssetSpec(os.path.join("build", "tools"), "dir"), AssetSpec(os.path.join("build", "compilers"), "dir"), AssetSpec(os.path.join("build", "ppc_binutils"), "dir"), + AssetSpec(os.path.join("build", "mips_binutils"), "dir"), ) @@ -120,6 +128,85 @@ def ensure_parent(path: str) -> None: os.makedirs(parent, exist_ok=True) +def seed_shared_file(shared_path: str, source_path: str, description: str) -> None: + ensure_parent(shared_path) + if os.path.isfile(shared_path) and not os.path.islink(shared_path): + if not filecmp.cmp(shared_path, source_path, shallow=False): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + return + if os.path.islink(shared_path): + if not same_symlink(shared_path, source_path): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + os.unlink(shared_path) + elif lexists(shared_path): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + shutil.copy2(source_path, shared_path) + + +def extract_zip_into(zip_path: str, output_dir: str) -> None: + os.makedirs(output_dir, exist_ok=True) + with zipfile.ZipFile(zip_path) as archive: + for member in archive.infolist(): + member_name = member.filename.rstrip("/") + if not member_name: + continue + + output_path = os.path.join(output_dir, *member_name.split("/")) + if member.is_dir(): + os.makedirs(output_path, exist_ok=True) + continue + + ensure_parent(output_path) + if os.path.exists(output_path): + continue + + with archive.open(member) as src, open(output_path, "wb") as dst: + shutil.copyfileobj(src, dst) + os.chmod(output_path, 0o755) + + +def seed_bootstrap_assets( + shared_root: str, xbox_xex: Optional[str], ps2_toolchain_zip: Optional[str] +) -> None: + if xbox_xex: + if not os.path.isfile(xbox_xex): + raise RuntimeError(f"Xbox XEX not found: {xbox_xex}") + seed_shared_file( + os.path.join( + shared_root, + "orig", + "EUROPEGERMILESTONE", + "NfsMWEuropeGerMilestone.xex", + ), + xbox_xex, + "Xbox XEX", + ) + + if ps2_toolchain_zip: + if not os.path.isfile(ps2_toolchain_zip): + raise RuntimeError(f"PS2 toolchain zip not found: {ps2_toolchain_zip}") + extract_zip_into(ps2_toolchain_zip, os.path.join(shared_root, "build", "compilers")) + expected_ee_gcc = os.path.join( + shared_root, + "build", + "compilers", + "PS2", + "ee-gcc2.9-991111", + "bin", + "ee-gcc.exe", + ) + if not os.path.isfile(expected_ee_gcc): + raise RuntimeError( + "PS2 toolchain zip did not produce build/compilers/PS2/ee-gcc2.9-991111/bin/ee-gcc.exe" + ) + + def merge_file(src: str, dst: str, relpath: str) -> None: ensure_parent(dst) if not os.path.exists(dst): @@ -342,18 +429,23 @@ def bootstrap_generated_files(worktree: str, version: str) -> None: objdiff_json = os.path.join(worktree, "objdiff.json") compile_commands = os.path.join(worktree, "compile_commands.json") config_target = os.path.join("build", version, "config.json") + configure_cmd = [sys.executable, "configure.py", "--version", version] - print(f"{worktree}: running configure.py") - run_command([sys.executable, "configure.py"], worktree, "configure.py") + print(f"{worktree}: running {' '.join(configure_cmd)}") + run_command(configure_cmd, worktree, "configure.py") if not os.path.isfile(build_ninja): raise RuntimeError(f"{worktree}: configure.py did not create build.ninja") - if not os.path.isfile(objdiff_json) or not os.path.isfile(compile_commands): + if ( + not os.path.isfile(config_target) + or not os.path.isfile(objdiff_json) + or not os.path.isfile(compile_commands) + ): print(f"{worktree}: generating {config_target} for local objdiff metadata") run_command(["ninja", config_target], worktree, f"ninja {config_target}") - print(f"{worktree}: rerunning configure.py") - run_command([sys.executable, "configure.py"], worktree, "configure.py") + print(f"{worktree}: rerunning {' '.join(configure_cmd)}") + run_command(configure_cmd, worktree, "configure.py") missing = [] if not os.path.isfile(objdiff_json): @@ -373,7 +465,10 @@ def bootstrap_worktrees( version: str, run_health: bool, smoke_build: Optional[str], + xbox_xex: Optional[str], + ps2_toolchain_zip: Optional[str], ) -> int: + seed_bootstrap_assets(shared_root, xbox_xex, ps2_toolchain_zip) link_assets(target_worktrees, seed_worktrees, shared_root) for worktree in target_worktrees: bootstrap_generated_files(worktree, version) @@ -418,6 +513,16 @@ def main() -> int: metavar="UNIT", help="Also run `decomp-workflow.py health --smoke-build UNIT` after bootstrap.", ) + parser.add_argument( + "--xbox-xex", + metavar="PATH", + help="Seed the shared Xbox XEX from a local file before linking/bootstrap.", + ) + parser.add_argument( + "--ps2-toolchain-zip", + metavar="PATH", + help="Extract a local PS2 EE-GCC zip into shared build/compilers before linking/bootstrap.", + ) args = parser.parse_args() common_dir = git_common_dir(root_dir) @@ -437,6 +542,8 @@ def main() -> int: args.version, args.health, args.smoke_build, + args.xbox_xex, + args.ps2_toolchain_zip, ) except RuntimeError as e: print(f"Error: {e}", file=sys.stderr) From 45403b80bb976309d78d85c7b32cb563dc3e9093 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 20 Mar 2026 16:46:11 +0100 Subject: [PATCH 028/372] tooling: add cross-platform build matrix checker Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 8 ++ tools/build_matrix.py | 307 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 tools/build_matrix.py diff --git a/AGENTS.md b/AGENTS.md index fc4f7af24..bf0cba804 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,8 +12,16 @@ ninja all_source # build all objects ninja # build all objects, hash check and progress report ninja baseline # generates baseline report for regression checking ninja changes # check for regressions after code changes (empty = no regressions) +python tools/build_matrix.py # sequential full `ninja` across GC/Xbox/PS2, then restore GOWE69 +python tools/build_matrix.py --all-source # sequential compile-only smoke check across GC/Xbox/PS2 ``` +Use `python tools/build_matrix.py` when you want one command that verifies the current +worktree across all supported platforms. It runs `configure.py --version ...` and the +selected ninja target sequentially, writes per-platform logs under `build//logs/`, +prints failure tails with the exact failing command, and restores the worktree to +`GOWE69` by default when it finishes. + ## Project Layout ``` diff --git a/tools/build_matrix.py b/tools/build_matrix.py new file mode 100644 index 000000000..135bce18c --- /dev/null +++ b/tools/build_matrix.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 + +""" +Run sequential build checks across supported platforms. + +Examples: + python tools/build_matrix.py + python tools/build_matrix.py --version GOWE69 --version SLES-53558-A124 + python tools/build_matrix.py --all-source +""" + +import argparse +import os +import subprocess +import sys +import time +from dataclasses import dataclass +from typing import List, Optional, Sequence + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) +DEFAULT_RESTORE_VERSION = "GOWE69" + + +@dataclass(frozen=True) +class PlatformCheck: + version: str + label: str + required_assets: Sequence[str] + + +@dataclass +class StepResult: + name: str + command: List[str] + returncode: int + elapsed: float + log_path: str + output: str + + @property + def ok(self) -> bool: + return self.returncode == 0 + + +@dataclass +class PlatformResult: + platform: PlatformCheck + configure: Optional[StepResult] = None + build: Optional[StepResult] = None + preflight_error: Optional[str] = None + + @property + def ok(self) -> bool: + return ( + self.preflight_error is None + and self.configure is not None + and self.configure.ok + and self.build is not None + and self.build.ok + ) + + +PLATFORMS = ( + PlatformCheck( + version="GOWE69", + label="GameCube", + required_assets=("orig/GOWE69/NFSMWRELEASE.ELF",), + ), + PlatformCheck( + version="EUROPEGERMILESTONE", + label="Xbox 360", + required_assets=("orig/EUROPEGERMILESTONE/NfsMWEuropeGerMilestone.xex",), + ), + PlatformCheck( + version="SLES-53558-A124", + label="PS2", + required_assets=("orig/SLES-53558-A124/NFS.ELF",), + ), +) + +PLATFORM_BY_VERSION = {platform.version: platform for platform in PLATFORMS} + + +def print_section(title: str) -> None: + print(f"\n== {title} ==", flush=True) + + +def tail_lines(text: str, count: int) -> str: + lines = text.rstrip().splitlines() + if len(lines) <= count: + return "\n".join(lines) + return "\n".join(lines[-count:]) + + +def run_logged(command: List[str], log_path: str) -> StepResult: + start = time.monotonic() + try: + completed = subprocess.run( + command, + cwd=ROOT_DIR, + capture_output=True, + text=True, + errors="replace", + ) + output = completed.stdout + if completed.stderr: + if output and not output.endswith("\n"): + output += "\n" + output += completed.stderr + returncode = completed.returncode + except OSError as exc: + output = str(exc) + returncode = 127 + elapsed = time.monotonic() - start + + os.makedirs(os.path.dirname(log_path), exist_ok=True) + with open(log_path, "w", encoding="utf-8") as log_file: + log_file.write(output) + + return StepResult( + name=os.path.basename(log_path), + command=command, + returncode=returncode, + elapsed=elapsed, + log_path=log_path, + output=output, + ) + + +def missing_assets(platform: PlatformCheck) -> List[str]: + missing = [] + for rel_path in platform.required_assets: + abs_path = os.path.join(ROOT_DIR, rel_path) + if not os.path.exists(abs_path): + missing.append(rel_path) + return missing + + +def describe_failure(step: StepResult, tail_count: int) -> None: + print(f"FAIL {step.name}: exit {step.returncode} in {step.elapsed:.2f}s") + print(f"Command: {' '.join(step.command)}") + print(f"Log: {step.log_path}") + if step.output.strip(): + print("--- output tail ---") + print(tail_lines(step.output, tail_count)) + + +def run_platform( + platform: PlatformCheck, build_target: Optional[str], jobs: int, tail_count: int +) -> PlatformResult: + result = PlatformResult(platform=platform) + logs_dir = os.path.join(ROOT_DIR, "build", platform.version, "logs") + + print_section(f"{platform.label} ({platform.version})") + + missing = missing_assets(platform) + if missing: + result.preflight_error = ( + "Missing required assets: " + + ", ".join(missing) + + " (hint: seed shared assets or run worktree bootstrap first)" + ) + print(f"FAIL preflight: {result.preflight_error}") + return result + + configure_cmd = [sys.executable, "configure.py", "--version", platform.version] + configure_log = os.path.join(logs_dir, "build-matrix-configure.log") + print(f"RUN configure: {' '.join(configure_cmd)}") + result.configure = run_logged(configure_cmd, configure_log) + if result.configure.ok: + print(f"OK configure: {result.configure.elapsed:.2f}s ({configure_log})") + else: + describe_failure(result.configure, tail_count) + return result + + build_cmd = ["ninja", "-j", str(jobs)] + if build_target is not None: + build_cmd.append(build_target) + build_name = build_target or "default" + build_log = os.path.join(logs_dir, f"build-matrix-{build_name}.log") + print(f"RUN build: {' '.join(build_cmd)}") + result.build = run_logged(build_cmd, build_log) + if result.build.ok: + print(f"OK build: {result.build.elapsed:.2f}s ({build_log})") + else: + describe_failure(result.build, tail_count) + + return result + + +def restore_version(version: str, tail_count: int) -> bool: + print_section(f"Restore {version}") + log_path = os.path.join(ROOT_DIR, "build", version, "logs", "build-matrix-restore.log") + step = run_logged([sys.executable, "configure.py", "--version", version], log_path) + if step.ok: + print(f"OK restore: {step.elapsed:.2f}s ({log_path})") + return True + + describe_failure(step, tail_count) + return False + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Check sequential builds across all supported platforms." + ) + parser.add_argument( + "--version", + dest="versions", + action="append", + choices=sorted(PLATFORM_BY_VERSION.keys()), + help="Limit the run to one or more versions (default: all platforms).", + ) + parser.add_argument( + "--all-source", + action="store_true", + help="Run `ninja all_source` instead of the default full `ninja`.", + ) + parser.add_argument( + "--jobs", + type=int, + default=1, + help="Parallelism passed to ninja (default: 1).", + ) + parser.add_argument( + "--tail", + type=int, + default=40, + help="How many output lines to print when a step fails (default: 40).", + ) + parser.add_argument( + "--restore-version", + default=DEFAULT_RESTORE_VERSION, + choices=sorted(PLATFORM_BY_VERSION.keys()), + help=f"Version to restore at the end (default: {DEFAULT_RESTORE_VERSION}).", + ) + parser.add_argument( + "--no-restore", + action="store_true", + help="Leave the worktree configured for the last checked version.", + ) + return parser.parse_args() + + +def print_summary( + results: Sequence[PlatformResult], restore_version_name: str, restore_ok: Optional[bool] +) -> None: + print_section("Summary") + for result in results: + if result.preflight_error is not None: + print(f"FAIL {result.platform.version}: {result.preflight_error}") + continue + if result.configure is None or not result.configure.ok: + assert result.configure is not None + print( + f"FAIL {result.platform.version}: configure exit {result.configure.returncode} " + f"({result.configure.elapsed:.2f}s)" + ) + continue + if result.build is None or not result.build.ok: + assert result.build is not None + print( + f"FAIL {result.platform.version}: build exit {result.build.returncode} " + f"({result.build.elapsed:.2f}s)" + ) + continue + total = result.configure.elapsed + result.build.elapsed + print(f"OK {result.platform.version}: {total:.2f}s") + + if restore_ok is not None: + status = "OK" if restore_ok else "FAIL" + print(f"{status:4} restore: {restore_version_name}") + + +args = parse_args() + + +def main() -> int: + selected_versions = args.versions or [platform.version for platform in PLATFORMS] + platforms = [PLATFORM_BY_VERSION[version] for version in selected_versions] + build_target = "all_source" if args.all_source else None + results: List[PlatformResult] = [] + restore_ok: Optional[bool] = None + + print(f"Root: {ROOT_DIR}") + print(f"Build target: {build_target or 'ninja default'}") + + try: + for platform in platforms: + results.append(run_platform(platform, build_target, args.jobs, args.tail)) + finally: + if not args.no_restore: + restore_ok = restore_version(args.restore_version, args.tail) + + print_summary(results, args.restore_version, restore_ok) + + if restore_ok is False: + return 1 + if any(not result.ok for result in results): + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 6b35f229c3bf26032fbedc9862e246abcec93fa5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 20 Mar 2026 16:51:05 +0100 Subject: [PATCH 029/372] tooling: extend health checks for Xbox and PS2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/decomp-workflow.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index 6be2b3d80..a00215d82 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -56,6 +56,9 @@ PS2_SYMBOLS = os.path.join(ROOT_DIR, "config", "SLES-53558-A124", "symbols.txt") GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") DEBUG_LINES = os.path.join(ROOT_DIR, "symbols", "debug_lines.txt") +X360_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "X360", "14.00.2110") +PS2_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "PS2", "ee-gcc2.9-991111") +MIPS_BINUTILS_DIR = os.path.join(ROOT_DIR, "build", "mips_binutils") DEFAULT_SMOKE_UNIT = "main/Speed/Indep/SourceLists/zCamera" DEBUG_SYMBOL_PROBE_MANGLED = "UpdateAll__6Cameraf" @@ -70,10 +73,32 @@ SHARED_ASSET_REQUIREMENTS = [ (os.path.join("build", "tools"), "downloaded tooling"), (os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "GameCube original ELF"), + ( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "Xbox original XEX", + ), (os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "PS2 original ELF"), (os.path.join("symbols", "Dwarf"), "DWARF dump"), ] +PLATFORM_BUILD_REQUIREMENTS = [ + ( + "x360-compiler", + X360_COMPILER_DIR, + "missing (seed build/compilers in this worktree for Xbox builds)", + ), + ( + "ps2-compiler", + PS2_COMPILER_DIR, + "missing (seed build/compilers in this worktree for PS2 builds)", + ), + ( + "ps2-binutils", + MIPS_BINUTILS_DIR, + "missing (seed build/mips_binutils in this worktree for PS2 builds)", + ), +] + class WorkflowError(RuntimeError): pass @@ -403,6 +428,14 @@ def build_shared_unit_cached(unit: str) -> str: except WorkflowError as e: report(False, "ghidra", str(e)) + print_section("Platform Build Inputs") + for label, abs_path, missing_detail in PLATFORM_BUILD_REQUIREMENTS: + report( + os.path.exists(abs_path), + label, + describe_path(abs_path) if os.path.exists(abs_path) else missing_detail, + ) + print_section("Debug Symbol Checks") try: gc_addr = lookup_symbol_address(GC_SYMBOLS, DEBUG_SYMBOL_PROBE_MANGLED) @@ -944,7 +977,7 @@ def build_parser() -> argparse.ArgumentParser: health = subparsers.add_parser( "health", - help="Check whether the current worktree is ready for GC and PS2 decomp work", + help="Check whether the current worktree is ready for GC, Xbox, and PS2 work", ) health.add_argument( "--full", From c9b94f54c6a3794b273a909b5dd3c14664783ee3 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Fri, 20 Mar 2026 18:50:31 +0100 Subject: [PATCH 030/372] disallow sub agents --- AGENTS.md | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bf0cba804..c55e27cb4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,16 +39,7 @@ objdiff.json Generated build/diff configuration ## Sub-Agent Usage -Sub-agents are allowed only for **read-only exploration** tasks such as: - -- searching the codebase for symbols, call sites, or include relationships -- inspecting decomp output, assembly, DWARF, PS2 dumps, or line mappings -- gathering context from Ghidra, `tools/decomp-workflow.py`, `lookup.py`, `decomp-diff.py`, or similar tools -- summarizing findings that help the main worker decide what to change - -Sub-agents must **not** write or edit code files, headers, configs, or other repository files. -All persistent file changes, decomp implementations, scaffolding, and follow-up fixes must be -done by the main worker after reviewing the read-only findings. +Sub-agents are **strictly prohibited**. Do not use sub-agents for any tasks (whether read-only exploration or editing). All work must be performed by the main worker directly. ## Forbidden Changes @@ -382,28 +373,10 @@ Examples: Do not batch up multiple percentage milestones into one commit — commit as each improvement lands. -## Parallel Sub-Agent Matching - -When working on a translation unit with multiple non-matching functions, use sub-agents selectively for **read-only exploration** around individual functions. Each sub-agent should focus on **exactly one function** — do not assign a sub-agent more than one function at a time. - -**Limit: never run more than 5 sub-agents concurrently.** Spawning too many at once causes resource contention and makes it harder to reason about progress. - -Guidelines: - -- Prefer solving difficult matching work in the main worker. Use sub-agents to inspect one function's context, diff, DWARF, or related call paths without editing files. -- Spawn a sub-agent per function only when the functions are independent (no shared edits to the same source lines). -- Sub-agents stay read-only. Let them inspect existing diff/context output rather than compiling or rebuilding. -- Do not sit idle waiting for sub-agents to finish. Continue with other independent investigation while they run. -- After a useful result lands and you make a real improvement, check the updated match percentage and commit if it improved. - ## Matching Philosophy You should take the Ghidra decompiler output for the initial translation step, get it to compile, make sure that the dwarf of the function matches and only then look for binary matching problems in the assembly. Be aware Ghidra usually gets the order of branches incorrect in if statements (it inverts the logic and the two bodies are swapped), this needs to be fixed to achieve bytematching status. -You may use sub-agents to gather read-only context during this process, but they must not -edit files. Treat their output as analysis input for the main worker, not as a path to -delegate source changes. - A function is only done when both objdiff and normalized DWARF are exact. Treat a 100% instruction match with a DWARF mismatch as unfinished work, not a near-complete result. From 9eef732bc56396b4dacb11bf303dc10f253471e8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sat, 21 Mar 2026 12:55:08 +0100 Subject: [PATCH 031/372] dwarf TU scan tool Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 90.6%: improve decomp-workflow::dwarf-scan signature filter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/decomp-workflow.py | 411 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index a00215d82..78e7fee04 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -16,12 +16,15 @@ python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-source python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zCamera -d UpdateAll --reloc-diffs all python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera --objdiff-status match python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zAttribSys -f 'Attrib::Class::RemoveCollection(Attrib::Collection *)' --full-diff python tools/decomp-workflow.py verify -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zCamera """ import argparse +import difflib import json import re import os @@ -45,6 +48,8 @@ make_abs, run_objdiff_json, ) +from lookup import _candidate_func_names, _sig_contains_name, read_text, split_functions +from split_dwarf_info import apply_umath_fixups SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -65,10 +70,12 @@ DEBUG_SYMBOL_PROBE_DEMANGLED = "Camera::UpdateAll(float)" DEBUG_SYMBOL_PROBE_GC_ADDR = "0x80065A84" REBUILT_DEBUG_LINE_RE = re.compile(r"^\s*([0-9A-Fa-f]+)\s*:") +DWARF_HEX_RE = re.compile(r"0x[0-9A-Fa-f]+") LOW_MATCH_PRIORITY_THRESHOLD = 60.0 VERY_LOW_MATCH_PRIORITY_THRESHOLD = 40.0 HIGH_MATCH_CLEANUP_THRESHOLD = 85.0 VERY_HIGH_MATCH_CLEANUP_THRESHOLD = 95.0 +FunctionBlock = Tuple[str, str, str, str] SHARED_ASSET_REQUIREMENTS = [ (os.path.join("build", "tools"), "downloaded tooling"), @@ -340,6 +347,355 @@ def load_dwarf_report( raise WorkflowError(f"dwarf-compare.py returned invalid JSON: {e}") +def load_dwarf_blocks( + path: str, folder_mode: bool, apply_split_fixups_in_ram: bool = False +) -> List[FunctionBlock]: + if folder_mode: + text = read_text(os.path.join(path, "functions.nothpp")) + else: + text = read_text(path) + if apply_split_fixups_in_ram: + text = apply_umath_fixups(text) + return split_functions(text) + + +def find_dwarf_function_blocks( + funcs: Sequence[FunctionBlock], query: str +) -> List[FunctionBlock]: + candidates = _candidate_func_names(query) + exact_matches: List[FunctionBlock] = [] + fuzzy_matches: List[FunctionBlock] = [] + + for func in funcs: + sig_line = func[2] + if sig_line == query: + exact_matches.append(func) + elif any(_sig_contains_name(sig_line, candidate) for candidate in candidates): + fuzzy_matches.append(func) + + if exact_matches: + return exact_matches + return fuzzy_matches + + +def choose_dwarf_function_block( + funcs: Sequence[FunctionBlock], query: str, label: str +) -> FunctionBlock: + matches = find_dwarf_function_blocks(funcs, query) + if not matches: + raise WorkflowError(f"{label}: function '{query}' not found.") + if len(matches) > 1: + preview = "\n".join(f" - {match[2]}" for match in matches[:8]) + extra = "" + if len(matches) > 8: + extra = f"\n ... {len(matches) - 8} more" + raise WorkflowError( + f"{label}: function query '{query}' matched multiple DWARF blocks.\n" + f"Use a more specific function name.\n{preview}{extra}" + ) + return matches[0] + + +def normalize_dwarf_line(line: str) -> str: + stripped = line.rstrip("\n").rstrip() + if stripped.startswith("// Range:"): + return "// Range: " + return DWARF_HEX_RE.sub("0xADDR", stripped) + + +def normalize_dwarf_block(block: str) -> List[str]: + return [normalize_dwarf_line(line) for line in block.splitlines()] + + +def count_dwarf_opcodes( + opcodes: Sequence[Tuple[str, int, int, int, int]] +) -> Dict[str, int]: + matching = 0 + original_only = 0 + rebuilt_only = 0 + changed_groups = 0 + for tag, i1, i2, j1, j2 in opcodes: + if tag == "equal": + matching += i2 - i1 + continue + changed_groups += 1 + if tag in ("replace", "delete"): + original_only += i2 - i1 + if tag in ("replace", "insert"): + rebuilt_only += j2 - j1 + return { + "matching_lines": matching, + "original_only_lines": original_only, + "rebuilt_only_lines": rebuilt_only, + "changed_groups": changed_groups, + } + + +def build_dwarf_scan_row( + row: Dict[str, Any], + original_funcs: Sequence[FunctionBlock], + rebuilt_funcs: Sequence[FunctionBlock], +) -> Dict[str, Any]: + function_name = str(row["name"]) + result: Dict[str, Any] = { + "function": function_name, + "symbol_name": row["symbol_name"], + "objdiff_status": row["status"], + "objdiff_match_percent": row["match_percent"], + "unmatched_bytes_est": row["unmatched_bytes_est"], + "size": row["size"], + } + + try: + original_block = choose_dwarf_function_block( + original_funcs, function_name, "original DWARF" + ) + rebuilt_block = choose_dwarf_function_block( + rebuilt_funcs, function_name, "rebuilt DWARF" + ) + original_lines = normalize_dwarf_block(original_block[3]) + rebuilt_lines = normalize_dwarf_block(rebuilt_block[3]) + matcher = difflib.SequenceMatcher(a=original_lines, b=rebuilt_lines) + counts = count_dwarf_opcodes(matcher.get_opcodes()) + total_lines = max(len(original_lines), len(rebuilt_lines), 1) + result.update( + { + "dwarf_status": "exact" + if original_lines == rebuilt_lines + else "mismatch", + "dwarf_match_percent": 100.0 * counts["matching_lines"] / total_lines, + "changed_groups": counts["changed_groups"], + "matching_lines": counts["matching_lines"], + "total_lines": total_lines, + "original_line_count": len(original_lines), + "rebuilt_line_count": len(rebuilt_lines), + "signature_match": normalize_dwarf_line(original_block[2]) + == normalize_dwarf_line(rebuilt_block[2]), + } + ) + except WorkflowError as e: + result.update( + { + "dwarf_status": "error", + "dwarf_match_percent": None, + "changed_groups": None, + "matching_lines": None, + "total_lines": None, + "original_line_count": None, + "rebuilt_line_count": None, + "signature_match": None, + "error": str(e), + } + ) + return result + + +def filter_dwarf_scan_rows( + rows: Sequence[Dict[str, Any]], dwarf_status: str +) -> List[Dict[str, Any]]: + if dwarf_status == "all": + return list(rows) + if dwarf_status == "problem": + return [row for row in rows if row["dwarf_status"] in ("mismatch", "error")] + return [row for row in rows if row["dwarf_status"] == dwarf_status] + + +def filter_dwarf_signature_rows( + rows: Sequence[Dict[str, Any]], signature_status: str +) -> List[Dict[str, Any]]: + if signature_status == "all": + return list(rows) + want_match = signature_status == "match" + return [ + row + for row in rows + if row.get("signature_match") is not None + and bool(row["signature_match"]) == want_match + ] + + +def sort_dwarf_scan_rows(rows: List[Dict[str, Any]]) -> None: + status_rank = {"error": 0, "mismatch": 1, "exact": 2} + rows.sort( + key=lambda row: ( + status_rank.get(str(row["dwarf_status"]), 3), + row["dwarf_match_percent"] + if row["dwarf_match_percent"] is not None + else -1.0, + 0 + if row.get("signature_match") is True + else 1 + if row.get("signature_match") is False + else 2, + -(row["changed_groups"] or 0), + -(row["unmatched_bytes_est"] or 0), + row["objdiff_match_percent"] + if row["objdiff_match_percent"] is not None + else -1.0, + row["function"].lower(), + ) + ) + + +def command_dwarf_scan(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + if not args.json: + print_section(f"DWARF Scan: {args.unit}") + ensure_shared_unit_output(args.unit) + + rebuilt_dwarf_path = ( + os.path.abspath(args.rebuilt_dwarf_file) if args.rebuilt_dwarf_file else None + ) + cleanup_rebuilt_dwarf = False + try: + if not rebuilt_dwarf_path: + rebuilt_dwarf_path = dtk_dwarf_dump(get_unit_build_output(args.unit)) + cleanup_rebuilt_dwarf = True + + data = run_objdiff_json( + OBJDIFF_CLI, + args.unit, + reloc_diffs=args.reloc_diffs, + root_dir=ROOT_DIR, + ) + rows = [ + row + for row in build_objdiff_symbol_rows(data) + if row["type"] == "function" and row["side"] == "left" + ] + if args.objdiff_status != "all": + rows = [row for row in rows if row["status"] == args.objdiff_status] + if args.search: + rows = [ + row + for row in rows + if fuzzy_match(args.search, row["name"]) + or fuzzy_match(args.search, row["symbol_name"]) + ] + if not rows: + raise WorkflowError("No functions match the given filters.") + + original_funcs = load_dwarf_blocks(GC_DWARF, folder_mode=True) + rebuilt_funcs = load_dwarf_blocks( + rebuilt_dwarf_path, folder_mode=False, apply_split_fixups_in_ram=True + ) + scan_rows = [ + build_dwarf_scan_row(row, original_funcs, rebuilt_funcs) for row in rows + ] + + summary = { + "scanned_functions": len(scan_rows), + "exact_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "exact" + ), + "mismatch_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "mismatch" + ), + "error_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "error" + ), + "byte_matched_dwarf_problems": sum( + 1 + for row in scan_rows + if row["objdiff_status"] == "match" + and row["dwarf_status"] in ("mismatch", "error") + ), + "signature_mismatch_functions": sum( + 1 for row in scan_rows if row.get("signature_match") is False + ), + } + + filtered_rows = filter_dwarf_scan_rows(scan_rows, args.dwarf_status) + filtered_rows = filter_dwarf_signature_rows( + filtered_rows, args.signature_status + ) + sort_dwarf_scan_rows(filtered_rows) + if args.limit is not None: + filtered_rows = filtered_rows[: args.limit] + + if args.json: + print( + json.dumps( + { + "unit": args.unit, + "summary": summary, + "rows": filtered_rows, + }, + indent=2, + ) + ) + return + + print( + f"Scanned {summary['scanned_functions']} function(s): " + f"{summary['exact_functions']} exact, " + f"{summary['mismatch_functions']} mismatched, " + f"{summary['error_functions']} errors." + ) + print( + "Byte-matched but DWARF-problem functions: " + f"{summary['byte_matched_dwarf_problems']}" + ) + print( + "Signature-mismatch functions: " + f"{summary['signature_mismatch_functions']}" + ) + + if not filtered_rows: + print("No functions match the given filters.") + return + + print() + print( + f"{'DSTAT':<8} {'DWARF':>7} {'SIG':>3} {'CHG':>4} {'OBJ':>7} {'OSTAT':<10} {'UNM':>6} FUNCTION" + ) + print("-" * 120) + for row in filtered_rows: + dwarf_percent = ( + f"{row['dwarf_match_percent']:.1f}%" + if row["dwarf_match_percent"] is not None + else "ERR" + ) + objdiff_percent = ( + f"{row['objdiff_match_percent']:.1f}%" + if row["objdiff_match_percent"] is not None + else "-" + ) + changed_groups = ( + str(row["changed_groups"]) if row["changed_groups"] is not None else "-" + ) + signature_state = ( + "yes" + if row.get("signature_match") is True + else "no" + if row.get("signature_match") is False + else "-" + ) + print( + f"{row['dwarf_status']:<8} {dwarf_percent:>7} {signature_state:>3} {changed_groups:>4} " + f"{objdiff_percent:>7} {row['objdiff_status']:<10} " + f"{row['unmatched_bytes_est']:>5}B {row['function']}" + ) + if args.show_errors and row.get("error"): + first_line = str(row["error"]).splitlines()[0] + print(f" error: {first_line}") + + print() + print( + "Tip: focus matched-byte functions first with " + "`python tools/decomp-workflow.py dwarf-scan " + f"-u {shlex.quote(args.unit)} --objdiff-status match`" + ) + if summary["signature_mismatch_functions"]: + print( + "Tip: add `--signature-status match` to focus body/local DWARF mismatches " + "instead of signature-only trouble." + ) + finally: + if cleanup_rebuilt_dwarf: + maybe_remove(rebuilt_dwarf_path) + + def lookup_symbol_address(symbols_file: str, mangled_name: str) -> Optional[str]: if not os.path.exists(symbols_file): return None @@ -1212,6 +1568,61 @@ def build_parser() -> argparse.ArgumentParser: ) dwarf.set_defaults(func=command_dwarf) + dwarf_scan = subparsers.add_parser( + "dwarf-scan", + help="Scan one translation unit and rank per-function DWARF problem areas", + ) + dwarf_scan.add_argument("-u", "--unit", required=True, help="Translation unit name") + dwarf_scan.add_argument( + "--search", + help="Only include functions whose name or symbol contains this text", + ) + dwarf_scan.add_argument( + "--objdiff-status", + choices=["all", "match", "nonmatching", "missing"], + default="all", + help="Filter functions by objdiff status before scanning (default: all)", + ) + dwarf_scan.add_argument( + "--dwarf-status", + choices=["all", "problem", "exact", "mismatch", "error"], + default="problem", + help="Filter scan results by DWARF outcome after scanning (default: problem)", + ) + dwarf_scan.add_argument( + "--signature-status", + choices=["all", "match", "mismatch"], + default="all", + help="Filter scan results by whether the DWARF signature already matches (default: all)", + ) + dwarf_scan.add_argument( + "--limit", + type=int, + default=20, + help="Maximum rows to print after sorting (default: 20)", + ) + dwarf_scan.add_argument( + "--json", + action="store_true", + help="Print the scan summary and rows as JSON", + ) + dwarf_scan.add_argument( + "--show-errors", + action="store_true", + help="Print one-line error details under rows that could not be compared", + ) + dwarf_scan.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode when loading unit symbols", + ) + dwarf_scan.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + dwarf_scan.set_defaults(func=command_dwarf_scan) + verify = subparsers.add_parser( "verify", help="Fail unless one function matches in both objdiff and DWARF", From eae477086f4bb4df1b004221b329f626c8caa8a4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sat, 21 Mar 2026 16:25:29 +0100 Subject: [PATCH 032/372] mac safe --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 547d049c6..1e78f634d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,12 @@ .idea/ .vs/ +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* + # Caches __pycache__ .mypy_cache From c4256f442b4f5a27e18908b271caca322d4d0b8f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 13:14:36 +0100 Subject: [PATCH 033/372] improve matching guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index c55e27cb4..3e97d44b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -462,6 +462,20 @@ register assignments but does NOT affect integer register assignments (and vice Every local that is NOT in the DWARF is a spurious temporary — remove it. - Every local that IS in the DWARF must exist in the source, even if you don't use the name. Name it exactly as the DWARF shows. +- When objdiff is already exact but a local only differs by lexical scope, try an equivalent + loop form that keeps the temporary inside the same block as the original DWARF. In practice, + changing a `for (...; ...; x = next)` into a `while (...) { T *next = ...; ...; x = next; }` + can fix DWARF-only scope mismatches without changing codegen. + +### Slot-pooled delete paths + +- If a recovered local/project type participates in `delete` paths or container/list teardown, + check whether the original type exposed inline `operator new` / `operator delete`. Missing + slot-pool-backed operators often makes GCC emit `__builtin_delete` instead of the original + allocator/free path and can also move destructor/delete DWARF ownership out of the TU. +- This applies even when the TU mostly allocates the type manually through `bOMalloc` or a + pool helper. Restoring the inline operators can still be necessary so `delete` expressions + and synthesized cleanup paths match the original code and DWARF. ### Virtual vs direct calls From 0dc1c8a25f0a64fd8d3d41378df2346d7bbd2a91 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 20:34:49 +0100 Subject: [PATCH 034/372] 37.5%: add FnDeltaQ EvalSQT bodies --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 496 +++++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h | 2 +- 2 files changed, 497 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 10528beb1..0696755f3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -6,6 +6,119 @@ namespace EAGL4Anim { +namespace { + +static const float kFloatZero = 0.0f; +static const float kFloatOne = 1.0f; +static const float kRangeScale16Bit = 3.0518044e-5f; +static const float kRangeScale15Bit = 6.1037019e-5f; +static const float kRangeScale8Bit = 7.8431377e-3f; +static const float kRangeScale7Bit = 1.5748032e-2f; + +static int GetFrameDeltaSize(const DeltaQ *deltaQ) { + return deltaQ->mNumBones * sizeof(DeltaQDelta); +} + +static int GetBinSize(const DeltaQ *deltaQ) { + return static_cast(AlignSize2((deltaQ->mNumBones * sizeof(DeltaQPhysical)) + + ((deltaQ->GetBinLength() - 1) * GetFrameDeltaSize(deltaQ)))); +} + +static DeltaQMinRange *GetMinRanges(DeltaQ *deltaQ) { + return deltaQ->GetMinRange(); +} + +static unsigned char *GetBinStart(DeltaQ *deltaQ) { + return &reinterpret_cast(GetMinRanges(deltaQ))[deltaQ->mNumBones * sizeof(DeltaQMinRange)]; +} + +static unsigned char *GetBin(DeltaQ *deltaQ, int binIdx) { + return &GetBinStart(deltaQ)[binIdx * GetBinSize(deltaQ)]; +} + +static DeltaQPhysical *GetPhysical(unsigned char *binData) { + return reinterpret_cast(binData); +} + +static DeltaQDelta *GetDelta(DeltaQ *deltaQ, unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[deltaQ->mNumBones * sizeof(DeltaQPhysical) + + (deltaIdx * GetFrameDeltaSize(deltaQ))]); +} + +static unsigned char *GetConstBoneIdx(DeltaQ *deltaQ) { + const int binSize = GetBinSize(deltaQ); + int numBins = deltaQ->mNumKeys >> deltaQ->GetBinLengthPower(); + unsigned char *s = &GetBin(deltaQ, 0)[binSize * numBins]; + int r = deltaQ->mNumKeys & deltaQ->GetBinLengthModMask(); + + if (r > 0) { + s = reinterpret_cast( + AlignSize2(reinterpret_cast(s + (deltaQ->mNumBones * sizeof(DeltaQPhysical)) + + ((r - 1) * GetFrameDeltaSize(deltaQ))))); + } + if (deltaQ->mNumBones == 0) { + s = reinterpret_cast(AlignSize2(reinterpret_cast(s))); + } + + return s; +} + +static DeltaQPhysical *GetConstPhysical(DeltaQ *deltaQ) { + return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx(deltaQ)[deltaQ->mNumConstBones]))); +} + +static void RecoverW(int signBit, UMath::Vector4 &q) { + float ndotn = q.x * q.x + q.y * q.y + q.z * q.z; + + if (ndotn <= kFloatOne) { + q.w = FastSqrt(kFloatOne - ndotn); + if (signBit) { + q.w = -q.w; + } + } else { + float len = FastSqrt(ndotn); + + q.x /= len; + q.y /= len; + q.z /= len; + q.w = kFloatZero; + } +} + +static void DecodePhysical(const DeltaQPhysical &physical, UMath::Vector4 &q) { + q.x = physical.mX * kRangeScale15Bit - kFloatOne; + q.y = physical.mY * kRangeScale16Bit - kFloatOne; + q.z = physical.mZ * kRangeScale16Bit - kFloatOne; + RecoverW(physical.mW, q); +} + +static void DecodeMinRange(const DeltaQMinRange &minRange, DeltaQMinRangef &minRangef) { + minRangef.mMin.x = minRange.mMin[0] * kRangeScale16Bit - kFloatOne; + minRangef.mMin.y = minRange.mMin[1] * kRangeScale16Bit - kFloatOne; + minRangef.mMin.z = minRange.mMin[2] * kRangeScale16Bit - kFloatOne; + + minRangef.mRange.x = 2.0f * (minRange.mRange[0] * kRangeScale16Bit) * kRangeScale7Bit; + minRangef.mRange.y = 2.0f * (minRange.mRange[1] * kRangeScale16Bit) * kRangeScale8Bit; + minRangef.mRange.z = 2.0f * (minRange.mRange[2] * kRangeScale16Bit) * kRangeScale8Bit; +} + +static void DecodeDelta(const DeltaQMinRange &minRange, const DeltaQDelta &delta, UMath::Vector4 &q) { + DeltaQMinRangef minRangef; + + DecodeMinRange(minRange, minRangef); + + q.x = minRangef.mMin.x + minRangef.mRange.x * delta.mX; + q.y = minRangef.mMin.y + minRangef.mRange.y * delta.mY; + q.z = minRangef.mMin.z + minRangef.mRange.z * delta.mZ; + RecoverW(delta.mW, q); +} + +static float *GetOutputQuat(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +} // namespace + FnDeltaQ::FnDeltaQ() : mPrevKey(-1), // mConstPhysical(nullptr), // @@ -22,6 +135,374 @@ void FnDeltaQ::Eval(float prevTime, float currTime, float *sqt) { EvalSQTMasked(currTime, nullptr, sqt); } +bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (!mBins) { + InitBuffersAsRequired(); + } + if (boneMask) { + return EvalSQTMasked(currTime, boneMask, sqt); + } + + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = GetBin(deltaQ, floorBinIdx); + DeltaQPhysical *floorPhys = GetPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DecodePhysical(floorPhys[ibone], mPrevQs[ibone]); + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + mPrevQs[ibone].x += delta.x; + mPrevQs[ibone].y += delta.y; + mPrevQs[ibone].z += delta.z; + } + } + } else if (prevDeltaIdx > floorDeltaIdx) { + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + mPrevQs[ibone].x -= delta.x; + mPrevQs[ibone].y -= delta.y; + mPrevQs[ibone].z -= delta.z; + } + } + } + + if (floorDeltaIdx == 0) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + RecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); + } + } else { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, floorDeltaIdx - 1); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + RecoverW(floorDelta[ibone].mW, mPrevQs[ibone]); + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaQPhysical *ceilPhys = GetPhysical(GetBin(deltaQ, ceilBinIdx)); + + if (ceilBinIdx != floorBinIdx) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + + DecodePhysical(ceilPhys[ibone], ceilq); + FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), + GetOutputQuat(sqt, boneIdxs[ibone])); + } + } else { + DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + UMath::Vector4 ceilq; + + DecodeDelta(mMinRanges[ibone], ceilDelta[ibone], delta); + ceilq.x = mPrevQs[ibone].x + delta.x; + ceilq.y = mPrevQs[ibone].y + delta.y; + ceilq.z = mPrevQs[ibone].z + delta.z; + RecoverW(ceilDelta[ibone].mW, ceilq); + + FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), + GetOutputQuat(sqt, boneIdxs[ibone])); + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])) = mPrevQs[ibone]; + } + } + + if (deltaQ->mNumConstBones != 0) { + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + UMath::Vector4 constq; + + DecodePhysical(mConstPhysical[ibone], constq); + *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; + } + } + + return true; +} + +bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + if (!mBins) { + InitBuffersAsRequired(); + } + + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = GetBin(deltaQ, floorBinIdx); + DeltaQPhysical *floorPhys = GetPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + DecodePhysical(floorPhys[ibone], mPrevQs[ibone]); + } + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + + DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + mPrevQs[ibone].x += delta.x; + mPrevQs[ibone].y += delta.y; + mPrevQs[ibone].z += delta.z; + } + } + } + } else if (prevDeltaIdx > floorDeltaIdx) { + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + + DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + mPrevQs[ibone].x -= delta.x; + mPrevQs[ibone].y -= delta.y; + mPrevQs[ibone].z -= delta.z; + } + } + } + } + + if (floorDeltaIdx == 0) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + RecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); + } + } + } else { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, floorDeltaIdx - 1); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + RecoverW(floorDelta[ibone].mW, mPrevQs[ibone]); + } + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaQPhysical *ceilPhys = GetPhysical(GetBin(deltaQ, ceilBinIdx)); + + if (ceilBinIdx == floorBinIdx) { + DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + UMath::Vector4 ceilq; + + DecodeDelta(mMinRanges[ibone], ceilDelta[ibone], delta); + ceilq.x = mPrevQs[ibone].x + delta.x; + ceilq.y = mPrevQs[ibone].y + delta.y; + ceilq.z = mPrevQs[ibone].z + delta.z; + RecoverW(ceilDelta[ibone].mW, ceilq); + + FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), + GetOutputQuat(sqt, boneIdxs[ibone])); + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + + DecodePhysical(ceilPhys[ibone], ceilq); + FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), + GetOutputQuat(sqt, boneIdxs[ibone])); + } + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])) = mPrevQs[ibone]; + } + } + } + + if (deltaQ->mNumConstBones != 0) { + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + if (boneMask->GetBone(mConstBoneIdxs[ibone])) { + UMath::Vector4 constq; + + DecodePhysical(mConstPhysical[ibone], constq); + *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; + } + } + } + + mPrevKey = -1; + return true; +} + bool FnDeltaQ::EvalWeights(float currTime, float *weights) { Eval(currTime, currTime, weights); return true; @@ -32,4 +513,19 @@ bool FnDeltaQ::EvalVel2D(float currTime, float *vel) { return true; } +void FnDeltaQ::InitBuffersAsRequired() { + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + + mMinRanges = GetMinRanges(deltaQ); + mBins = GetBinStart(deltaQ); + mBinSize = GetBinSize(deltaQ); + mConstBoneIdxs = GetConstBoneIdx(deltaQ); + mConstPhysical = GetConstPhysical(deltaQ); + + if (deltaQ->mNumBones != 0) { + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + } +} + }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h index 80a3abe6e..7d86a0011 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h @@ -73,7 +73,7 @@ class FnDeltaQ : public FnAnimMemoryMap { protected: virtual bool EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt); - void InitBuffersAsRequired() {} + void InitBuffersAsRequired(); DeltaQMinRange *mMinRanges; // offset 0x10, size 0x4 unsigned char *mBins; // offset 0x14, size 0x4 From ed27476f01a7e26455b261b56ae81522dd27bac9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 20:41:41 +0100 Subject: [PATCH 035/372] 40.3%: add FnDeltaSingleQ EvalSQT bodies --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 571 ++++++++++++++++++ .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.h | 5 +- 2 files changed, 575 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index e69de29bb..84deebad9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -0,0 +1,571 @@ +#include "FnDeltaSingleQ.h" +#include "AnimTypeId.h" +#include "MemoryPoolManager.h" +#include "Speed/Indep/Src/EAGL4Anim/AnimTypeId.h" +#include "Speed/Indep/Src/EAGL4Anim/AnimUtil.h" + +namespace EAGL4Anim { + +namespace { + +static const float kSingleQFloatZero = 0.0f; +static const float kSingleQFloatOne = 1.0f; +static const float kSingleQHalf = 0.5f; +static const float kSingleQPi = 3.1415927f; +static const float kSingleQAngleScale16Bit = 9.5875265e-5f; +static const float kSingleQRangeScale16Bit = 3.0518044e-5f; +static const float kSingleQRangeScale8Bit = 7.8431377e-3f; +static const float kSingleQRangeScale4Bit = 0.13333334f; + +static int GetSingleQFrameDeltaSize(const DeltaSingleQ *deltaQ) { + return deltaQ->mNumBones * sizeof(DeltaSingleQDelta); +} + +static int GetSingleQBinSize(const DeltaSingleQ *deltaQ) { + return static_cast(AlignSize2((deltaQ->mNumBones * sizeof(DeltaSingleQPhysical)) + + ((deltaQ->GetBinLength() - 1) * GetSingleQFrameDeltaSize(deltaQ)))); +} + +static DeltaSingleQMinRange *GetSingleQMinRanges(DeltaSingleQ *deltaQ) { + return deltaQ->GetMinRange(); +} + +static unsigned char *GetSingleQBinStart(DeltaSingleQ *deltaQ) { + return &reinterpret_cast(GetSingleQMinRanges(deltaQ))[deltaQ->mNumBones * sizeof(DeltaSingleQMinRange)]; +} + +static unsigned char *GetSingleQBin(DeltaSingleQ *deltaQ, int binIdx) { + return &GetSingleQBinStart(deltaQ)[binIdx * GetSingleQBinSize(deltaQ)]; +} + +static DeltaSingleQPhysical *GetSingleQPhysical(unsigned char *binData) { + return reinterpret_cast(binData); +} + +static DeltaSingleQDelta *GetSingleQDelta(DeltaSingleQ *deltaQ, unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[deltaQ->mNumBones * sizeof(DeltaSingleQPhysical) + + (deltaIdx * GetSingleQFrameDeltaSize(deltaQ))]); +} + +static void NormalizeSingleQQuat(UMath::Vector4 &q) { + float s = kSingleQFloatOne / FastSqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + + q.x *= s; + q.y *= s; + q.z *= s; + q.w *= s; +} + +static void SingleQEulToQuat(const float *eulData, float *quatData) { + float ti = eulData[0] * kSingleQHalf; + float tj = eulData[1] * kSingleQHalf; + float th = eulData[2] * kSingleQHalf; + float ci = cosf(ti); + float cj = cosf(tj); + float ch = cosf(th); + float si = sinf(ti); + float sj = sinf(tj); + float sh = sinf(th); + float cc = ci * cj; + float cs = ci * sj; + float sc = si * cj; + float ss = si * sj; + + quatData[0] = sc * ch - cs * sh; + quatData[1] = cs * ch + sc * sh; + quatData[2] = cc * sh - ss * ch; + quatData[3] = cc * ch + ss * sh; +} + +static void SingleQQuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { + float awby = a.w * b.y; + float axbw = a.x * b.w; + float awbw = a.w * b.w; + float naxby = -(a.x * b.y); + + result.x = axbw * c.w - awby * c.z; + result.y = axbw * c.z + awby * c.w; + result.z = naxby * c.w + awbw * c.z; + result.w = -naxby * c.z + awbw * c.w; +} + +static void SingleQQuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.w * b.x + a.x * b.w; + result.y = a.w * b.y + a.x * b.z; + result.z = -(a.x * b.y) + a.w * b.z; + result.w = -(a.x * b.x) + a.w * b.w; +} + +static void SingleQQuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w - a.y * b.z; + result.y = a.x * b.z + a.y * b.w; + result.z = a.z * b.w + a.w * b.z; + result.w = -(a.z * b.z) + a.w * b.w; +} + +static void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSingleQMinRangef &minRangef) { + minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit - kSingleQPi; + minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit - kSingleQPi; + minRangef.mMin[0] = minRange.mMin[0] * kSingleQRangeScale16Bit - kSingleQFloatOne; + minRangef.mMin[1] = minRange.mMin[1] * kSingleQRangeScale16Bit - kSingleQFloatOne; + minRangef.mRange[0] = 2.0f * (minRange.mRange[0] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; + minRangef.mRange[1] = 2.0f * (minRange.mRange[1] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; + minRangef.mIndex = static_cast(minRange.mIndex); +} + +static void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { + q.x = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.z = kSingleQFloatZero; + q.w = physical.mW * kSingleQRangeScale8Bit - kSingleQFloatOne; + + if (index == 0) { + q.x = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + } else if (index == 1) { + q.y = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + } else { + q.z = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + } +} + +static void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, const DeltaSingleQDelta &delta, UMath::Vector4 &q) { + DeltaSingleQMinRangef minRangef; + float v = minRangef.mMin[0]; + float w = minRangef.mMin[1]; + + DecodeSingleQMinRange(minRange, minRangef); + + v = minRangef.mRange[0] * delta.mV + minRangef.mMin[0]; + w = minRangef.mRange[1] * delta.mW + minRangef.mMin[1]; + + q.x = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.z = kSingleQFloatZero; + q.w = w; + + if (minRangef.mIndex == 0) { + q.x = v; + } else if (minRangef.mIndex == 1) { + q.y = v; + } else { + q.z = v; + } +} + +static void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, + const UMath::Vector4 &post, UMath::Vector4 &result) { + if (index == 1) { + SingleQQuatMultXxYxZ(pre, mid, post, result); + } else if (index == 0) { + SingleQQuatMultXxQ(mid, post, result); + } else { + SingleQQuatMultQxZ(pre, mid, result); + } +} + +static float *GetSingleQOutputQuat(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +} // namespace + +FnDeltaSingleQ::FnDeltaSingleQ() + : mMinRanges(nullptr), // + mBins(nullptr), // + mBinSize(-1), // + mPrevKey(-1), // + mPrevQBlock(nullptr), // + mPrevQs(nullptr), // + mPreMultQs(nullptr), // + mPostMultQs(nullptr) { + mType = AnimTypeId::ANIM_DELTASINGLEQ; +} + +void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (!mPrevQs) { + InitBuffersAsRequired(); + } + if (boneMask) { + return EvalSQTMasked(currTime, boneMask, sqt); + } + + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = GetSingleQBin(deltaQ, floorBinIdx); + DeltaSingleQPhysical *floorPhys = GetSingleQPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorKey < mPrevKey) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DecodeSingleQPhysical(floorPhys[ibone], mMinRanges[ibone].mIndex, mPrevQs[ibone]); + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaSingleQDelta *floorDelta = GetSingleQDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 deltaf; + + DecodeSingleQDelta(mMinRanges[ibone], floorDelta[ibone], deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; + mPrevQs[ibone].w += deltaf.w; + } + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(GetSingleQBin(deltaQ, ceilBinIdx)); + + if (ceilBinIdx == floorBinIdx) { + DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + UMath::Vector4 outq; + + DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + ceilq.w += mPrevQs[ibone].w; + + interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; + interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; + interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; + interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + UMath::Vector4 outq; + + DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); + interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; + interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; + interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; + interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 outq; + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], outq); + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + } + } + + return true; +} + +bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + if (!mPrevQs) { + InitBuffersAsRequired(); + } + + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = GetSingleQBin(deltaQ, floorBinIdx); + DeltaSingleQPhysical *floorPhys = GetSingleQPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorKey < mPrevKey) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + DecodeSingleQPhysical(floorPhys[ibone], mMinRanges[ibone].mIndex, mPrevQs[ibone]); + } + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaSingleQDelta *floorDelta = GetSingleQDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 deltaf; + + DecodeSingleQDelta(mMinRanges[ibone], floorDelta[ibone], deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; + mPrevQs[ibone].w += deltaf.w; + } + } + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(GetSingleQBin(deltaQ, ceilBinIdx)); + + if (ceilBinIdx == floorBinIdx) { + DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + UMath::Vector4 outq; + + DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + ceilq.w += mPrevQs[ibone].w; + + interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; + interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; + interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; + interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + UMath::Vector4 outq; + + DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); + interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; + interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; + interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; + interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + } + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 outq; + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], outq); + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + } + } + } + + return true; +} + +void FnDeltaSingleQ::InitBuffersAsRequired() { + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + + mMinRanges = GetSingleQMinRanges(deltaQ); + mBins = GetSingleQBinStart(deltaQ); + mBinSize = GetSingleQBinSize(deltaQ); + + if (deltaQ->mNumBones != 0) { + float eul[3]; + + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + mPreMultQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); + mPostMultQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaSingleQMinRangef minRangef; + + DecodeSingleQMinRange(mMinRanges[ibone], minRangef); + + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; + + if (minRangef.mIndex == 0) { + eul[0] = kSingleQFloatZero; + eul[1] = minRangef.mConst0; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (minRangef.mIndex == 1) { + eul[0] = minRangef.mConst0; + eul[1] = kSingleQFloatZero; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + + eul[0] = kSingleQFloatZero; + eul[1] = kSingleQFloatZero; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else { + eul[0] = minRangef.mConst0; + eul[1] = minRangef.mConst1; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + } + } + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h index a4e3dfa16..d84932b65 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h @@ -56,13 +56,16 @@ class FnDeltaSingleQ : public FnAnimMemoryMap { return true; } + // Overrides: FnAnim + void Eval(float prevTime, float currTime, float *sqt) override; + // Overrides: FnAnim bool EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) override; protected: virtual bool EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt); - void InitBuffersAsRequired() {} + void InitBuffersAsRequired(); DeltaSingleQMinRange *mMinRanges; // offset 0x10, size 0x4 unsigned char *mBins; // offset 0x14, size 0x4 From 79594075508d43ef7c36781d19567183bbf765c0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 20:47:25 +0100 Subject: [PATCH 036/372] 41.4%: add FnStatelessQ first pass --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 283 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h | 4 +- src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp | 27 ++ src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h | 28 +- 4 files changed, 334 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index e69de29bb..af32a993d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -0,0 +1,283 @@ +#include "FnStatelessQ.h" +#include "AnimTypeId.h" +#include "FnStatelessF3.h" +#include "StatelessQ.h" + +namespace EAGL4Anim { + +namespace { + +static float UncompressStatelessQValue(unsigned short value) { + union { + unsigned int u; + float f; + } bits; + + bits.u = ((value & 0x8000) << 16) | ((value & 0x7FFF) << 15); + return bits.f; +} + +static float *GetStatelessQOutput(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +static void LoadStatelessQ(unsigned short *frameData, UMath::Vector4 &q) { + q.x = UncompressStatelessQValue(frameData[0]); + q.y = UncompressStatelessQValue(frameData[1]); + q.z = UncompressStatelessQValue(frameData[2]); + q.w = UncompressStatelessQValue(frameData[3]); +} + +} // namespace + +FnStatelessQ::FnStatelessQ() + : mUseFPS(false), // + mFPS(0), // + mBoneMask(nullptr) { + mType = AnimTypeId::ANIM_STATELESSQ; + mPrevKey = 0; +} + +FnStatelessQ::~FnStatelessQ() {} + +void FnStatelessQ::SetAnimMemoryMap(AnimMemoryMap *anim) { + mpAnim = anim; +} + +bool FnStatelessQ::GetLength(float &timeLength) const { + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + + timeLength = static_cast(statelessQ->GetNumFrames()); + return true; +} + +void FnStatelessQ::Eval(float, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + + if (mUseFPS) { + currTime *= mFPS; + } + + int floorTime = FloatToInt(currTime); + int floorKey; + bool slerpReqd; + float scale = 0.0f; + + if (!statelessQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= statelessQ->mNumKeys) { + floorKey = statelessQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + + slerpReqd = floorKey < statelessQ->mNumKeys - 1; + if (slerpReqd) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } + } else if (floorTime < statelessQ->mTimes[0]) { + floorKey = 0; + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + scale = currTime / static_cast(statelessQ->mTimes[0]); + } + } else { + int timeIndex = 0; + + if (mPrevKey != 0) { + timeIndex = mPrevKey - 1; + } + if (floorTime < statelessQ->mTimes[timeIndex]) { + while (timeIndex > 0 && floorTime < statelessQ->mTimes[timeIndex]) { + timeIndex--; + } + } else { + while (timeIndex < statelessQ->mNumKeys - 2 && floorTime >= statelessQ->mTimes[timeIndex + 1]) { + timeIndex++; + } + } + + floorKey = timeIndex + 1; + if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + scale = currTime / static_cast(statelessQ->mTimes[0]); + } + } else { + float floorKeyTime = static_cast(statelessQ->mTimes[floorKey - 1]); + + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(statelessQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + if (floorKey >= statelessQ->mNumKeys - 1) { + slerpReqd = false; + } + } + + mPrevKey = static_cast(floorKey); + + if (boneMask) { + EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } else { + if (mBoneMask) { + mBoneMask = nullptr; + } + + unsigned short *dataBuf = statelessQ->GetData(); + unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); + unsigned char *boneIdxs = statelessQ->mBoneIdxs; + int nBones = statelessQ->mNumBones; + + if (!slerpReqd) { + for (int ibone = 0; ibone < nBones; ibone++) { + float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); + + q[0] = UncompressStatelessQValue(frameData[0]); + q[1] = UncompressStatelessQValue(frameData[1]); + q[2] = UncompressStatelessQValue(frameData[2]); + q[3] = UncompressStatelessQValue(frameData[3]); + frameData += 4; + } + } else { + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector4 prevQ; + UMath::Vector4 nextQ; + float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); + + LoadStatelessQ(frameData, prevQ); + LoadStatelessQ(nextFrameData, nextQ); + + q[0] = prevQ.x + (nextQ.x - prevQ.x) * scale; + q[1] = prevQ.y + (nextQ.y - prevQ.y) * scale; + q[2] = prevQ.z + (nextQ.z - prevQ.z) * scale; + q[3] = prevQ.w + (nextQ.w - prevQ.w) * scale; + + frameData += 4; + nextFrameData += 4; + } + } + + if (statelessQ->mNumConstBones != 0) { + unsigned short *constBuf = statelessQ->GetConstData(dataBuf); + unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); + + for (int ibone = 0; ibone < statelessQ->mNumConstBones; ibone++) { + float *q = GetStatelessQOutput(sqt, *constIdxs++); + + q[0] = UncompressStatelessQValue(constBuf[0]); + q[1] = UncompressStatelessQValue(constBuf[1]); + q[2] = UncompressStatelessQValue(constBuf[2]); + q[3] = UncompressStatelessQValue(constBuf[3]); + constBuf += 4; + } + } + } + + if (statelessQ->mF3Ptr) { + reinterpret_cast(statelessQ->mF3Ptr)->EvalSQTfast(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } + + return true; +} + +bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + unsigned short *dataBuf = statelessQ->GetData(); + unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); + unsigned char *boneIdxs = statelessQ->mBoneIdxs; + int nBones = statelessQ->mNumBones; + + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + } + + if (!slerpReqd || floorKey >= statelessQ->mNumKeys - 1) { + for (int ibone = 0; ibone < nBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (boneMask->GetBone(boneIdx)) { + float *q = GetStatelessQOutput(sqt, boneIdx); + + q[0] = UncompressStatelessQValue(frameData[0]); + q[1] = UncompressStatelessQValue(frameData[1]); + q[2] = UncompressStatelessQValue(frameData[2]); + q[3] = UncompressStatelessQValue(frameData[3]); + } + frameData += 4; + } + } else { + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (boneMask->GetBone(boneIdx)) { + UMath::Vector4 prevQ; + UMath::Vector4 nextQ; + float *q = GetStatelessQOutput(sqt, boneIdx); + + LoadStatelessQ(frameData, prevQ); + LoadStatelessQ(nextFrameData, nextQ); + + q[0] = prevQ.x + (nextQ.x - prevQ.x) * scale; + q[1] = prevQ.y + (nextQ.y - prevQ.y) * scale; + q[2] = prevQ.z + (nextQ.z - prevQ.z) * scale; + q[3] = prevQ.w + (nextQ.w - prevQ.w) * scale; + } + frameData += 4; + nextFrameData += 4; + } + } + + if (statelessQ->mNumConstBones != 0) { + unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); + unsigned short *constBuf = statelessQ->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessQ->mNumConstBones; ibone++) { + unsigned char boneIdx = constIdxs[ibone]; + + if (boneMask->GetBone(boneIdx)) { + float *q = GetStatelessQOutput(sqt, boneIdx); + + q[0] = UncompressStatelessQValue(constBuf[0]); + q[1] = UncompressStatelessQValue(constBuf[1]); + q[2] = UncompressStatelessQValue(constBuf[2]); + q[3] = UncompressStatelessQValue(constBuf[3]); + } + constBuf += 4; + } + } + + return true; +} + +void FnStatelessQ::UseFPS(bool u) { + mUseFPS = u; + if (mFPS != 0 || !u) { + return; + } + GetAttribute(AttributeId(AttributeId::ID_FPS), mFPS); +} + +unsigned short FnStatelessQ::GetTargetCheckSum() const { + return mpAnim->GetTargetCheckSum(); +} + +const AttributeBlock *FnStatelessQ::GetAttributes() const { + return reinterpret_cast(mpAnim)->GetAttributeBlock(); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h index e8d0e0889..8bb95746a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h @@ -53,10 +53,10 @@ class FnStatelessQ : public FnAnimMemoryMap { unsigned char GetFPS() const; // Overrides: FnAnim - unsigned short GetTargetCheckSum() const override {} + unsigned short GetTargetCheckSum() const override; // Overrides: FnAnim - const AttributeBlock *GetAttributes() const override {} + const AttributeBlock *GetAttributes() const override; protected: // Overrides: FnAnim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp index e69de29bb..4917d606a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp @@ -0,0 +1,27 @@ +#include "StatelessQ.h" +#include "FnStatelessF3.h" +#include "FnStatelessQ.h" +#include "StatelessF3.h" + +namespace EAGL4Anim { + +void StatelessQ::InitAnimMemoryMap(AnimMemoryMap *anim) { + StatelessQ *statelessQ = reinterpret_cast(anim); + FnStatelessQ fnStatelessQ; + FnStatelessQ *fnStatelessQPtr = reinterpret_cast(statelessQ->GetFnLocation()); + + *reinterpret_cast(fnStatelessQPtr) = *reinterpret_cast(&fnStatelessQ); + fnStatelessQPtr->SetAnimMemoryMap(anim); + + if (statelessQ->mF3Ptr) { + StatelessF3 *statelessF3 = reinterpret_cast(statelessQ->mF3Ptr); + FnStatelessF3 fnStatelessF3; + FnStatelessF3 *fnStatelessF3Ptr = reinterpret_cast(statelessF3->GetFnLocation()); + + *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + fnStatelessF3Ptr->SetAnimMemoryMap(statelessF3); + statelessQ->mF3Ptr = fnStatelessF3Ptr; + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h index 2176952e9..213f1e6b0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h @@ -11,17 +11,33 @@ namespace EAGL4Anim { // total size: 0x18 struct StatelessQ : public AnimMemoryMap { - const AttributeBlock *GetAttributeBlock() const {} + const AttributeBlock *GetAttributeBlock() const { + return mAttributeBlock; + } - int GetNumFrames() const {} + int GetNumFrames() const { + if (!mTimes) { + return mNumKeys; + } else { + return mTimes[mNumKeys - 2] + 1; + } + } - unsigned short *GetData() {} + unsigned short *GetData() { + return reinterpret_cast(&this[1]); + } - unsigned short *GetFrameData(unsigned short *dataBuf, int frameIdx) {} + unsigned short *GetFrameData(unsigned short *dataBuf, int frameIdx) { + return &dataBuf[frameIdx * 4 * mNumBones]; + } - unsigned short *GetConstData(unsigned short *dataBuf) {} + unsigned short *GetConstData(unsigned short *dataBuf) { + return &dataBuf[mNumKeys * 4 * mNumBones]; + } - unsigned char *GetConstBoneIdx() {} + unsigned char *GetConstBoneIdx() { + return &mBoneIdxs[mNumBones]; + } static int ComputeSize(int numKeys, int numBones, int numConstBones, bool useKeyFrames) {} From 2a1e87a191c3486a95d72c08a677f1e9b46949aa Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 20:49:55 +0100 Subject: [PATCH 037/372] 42.7%: add FnStatelessF3 first pass --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 278 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h | 4 +- src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp | 15 + src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h | 44 ++- 4 files changed, 330 insertions(+), 11 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index e69de29bb..62e6b2880 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -0,0 +1,278 @@ +#include "FnStatelessF3.h" +#include "AnimTypeId.h" +#include "StatelessF3.h" + +namespace EAGL4Anim { + +namespace { + +static unsigned char GetStatelessF3BoneIndex(unsigned short dofIdx) { + return static_cast(dofIdx / 12); +} + +static void UnquantizeStatelessF3(const StatelessF3::DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) { + result.x = dofInfo.mRange[0] * frameBuf[0]; + result.y = dofInfo.mRange[1] * frameBuf[1]; + result.z = dofInfo.mRange[2] * frameBuf[2]; +} + +} // namespace + +FnStatelessF3::FnStatelessF3() + : mUseFPS(false), // + mFPS(0), // + mBoneMask(nullptr) { + mType = AnimTypeId::ANIM_STATELESSF3; + mPrevKey = 0; +} + +FnStatelessF3::~FnStatelessF3() {} + +void FnStatelessF3::SetAnimMemoryMap(AnimMemoryMap *anim) { + mpAnim = anim; +} + +bool FnStatelessF3::GetLength(float &timeLength) const { + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + + timeLength = static_cast(statelessF3->GetNumFrames()); + return true; +} + +void FnStatelessF3::Eval(float, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + + if (mUseFPS) { + currTime *= mFPS; + } + + int floorTime = FloatToInt(currTime); + int floorKey; + bool slerpReqd; + float scale = 0.0f; + + if (!statelessF3->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= statelessF3->mNumKeys) { + floorKey = statelessF3->mNumKeys - 1; + } else { + floorKey = floorTime; + } + + slerpReqd = floorKey < statelessF3->mNumKeys - 1; + if (slerpReqd) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } + } else if (floorTime < statelessF3->mTimes[0]) { + floorKey = 0; + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + scale = currTime / static_cast(statelessF3->mTimes[0]); + } + } else { + int timeIndex = 0; + + if (mPrevKey != 0) { + timeIndex = mPrevKey - 1; + } + if (floorTime < statelessF3->mTimes[timeIndex]) { + while (timeIndex > 0 && floorTime < statelessF3->mTimes[timeIndex]) { + timeIndex--; + } + } else { + while (timeIndex < statelessF3->mNumKeys - 2 && floorTime >= statelessF3->mTimes[timeIndex + 1]) { + timeIndex++; + } + } + + floorKey = timeIndex + 1; + if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + scale = currTime / static_cast(statelessF3->mTimes[0]); + } + } else { + float floorKeyTime = static_cast(statelessF3->mTimes[floorKey - 1]); + + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(statelessF3->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + if (floorKey >= statelessF3->mNumKeys - 1) { + slerpReqd = false; + } + } + + mPrevKey = static_cast(floorKey); + + if (!boneMask) { + if (mBoneMask) { + mBoneMask = nullptr; + } + EvalSQTfast(currTime, sqt, nullptr, slerpReqd, floorKey, scale); + } else { + EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } + + return true; +} + +bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; + unsigned char boneIdxs[120]; + + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + } + + for (int ibone = 0; ibone < nBones; ibone++) { + boneIdxs[ibone] = GetStatelessF3BoneIndex(dofIdxs[ibone]); + } + + if (!slerpReqd) { + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector3 value; + int index = dofIdxs[ibone]; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, value); + sqt[index + 0] = value.x; + sqt[index + 1] = value.y; + sqt[index + 2] = value.z; + } + frameData += 3; + } + } else { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector3 prev; + UMath::Vector3 next; + int index = dofIdxs[ibone]; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; + sqt[index + 1] = prev.y + (next.y - prev.y) * scale; + sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + } + frameData += 3; + nextFrameData += 3; + } + } + + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + unsigned char boneIdx = GetStatelessF3BoneIndex(constIdxs[ibone]); + + if (boneMask->GetBone(boneIdx)) { + int index = constIdxs[ibone]; + + sqt[index + 0] = constBuf[0]; + sqt[index + 1] = constBuf[1]; + sqt[index + 2] = constBuf[2]; + } + constBuf += 3; + } + } + + return true; +} + +bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + if (boneMask) { + return EvalSQTMask(0.0f, sqt, boneMask, slerpReqd, floorKey, scale); + } + + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; + + mPrevKey = static_cast(floorKey); + + if (!slerpReqd) { + for (int ibone = 0; ibone < nBones; ibone++) { + int index = dofIdxs[ibone]; + + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } + } else { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector3 prev; + UMath::Vector3 next; + int index = dofIdxs[ibone]; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; + sqt[index + 1] = prev.y + (next.y - prev.y) * scale; + sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + + frameData += 3; + nextFrameData += 3; + } + } + + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + int index = constIdxs[ibone]; + + sqt[index + 0] = constBuf[0]; + sqt[index + 1] = constBuf[1]; + sqt[index + 2] = constBuf[2]; + constBuf += 3; + } + } + + return true; +} + +void FnStatelessF3::UseFPS(bool u) { + mUseFPS = u; + if (mFPS != 0 || !u) { + return; + } + GetAttribute(AttributeId(AttributeId::ID_FPS), mFPS); +} + +unsigned short FnStatelessF3::GetTargetCheckSum() const { + return mpAnim->GetTargetCheckSum(); +} + +const AttributeBlock *FnStatelessF3::GetAttributes() const { + return reinterpret_cast(mpAnim)->GetAttributeBlock(); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h index 98b55525d..54ffbaa02 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h @@ -54,10 +54,10 @@ class FnStatelessF3 : public FnAnimMemoryMap { unsigned char GetFPS() const; // Overrides: FnAnim - unsigned short GetTargetCheckSum() const override {} + unsigned short GetTargetCheckSum() const override; // Overrides: FnAnim - const AttributeBlock *GetAttributes() const override {} + const AttributeBlock *GetAttributes() const override; protected: bool EvalSQTMask(float currTime, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale); diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp index e69de29bb..0369d696e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp @@ -0,0 +1,15 @@ +#include "StatelessF3.h" +#include "FnStatelessF3.h" + +namespace EAGL4Anim { + +void StatelessF3::InitAnimMemoryMap(AnimMemoryMap *anim) { + StatelessF3 *statelessF3 = reinterpret_cast(anim); + FnStatelessF3 fnStatelessF3; + FnStatelessF3 *fnStatelessF3Ptr = reinterpret_cast(statelessF3->GetFnLocation()); + + *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + fnStatelessF3Ptr->SetAnimMemoryMap(statelessF3); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h index 3ac7433aa..9b109fc83 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Src/EAGL4Anim/AnimMemoryMap.h" +#include "Speed/Indep/Src/EAGL4Anim/AnimUtil.h" namespace EAGL4Anim { @@ -17,25 +18,50 @@ struct StatelessF3 : public AnimMemoryMap { float mRange[4]; // offset 0x10, size 0x10 }; - const AttributeBlock *GetAttributeBlock() const {} + const AttributeBlock *GetAttributeBlock() const { + return mAttributeBlock; + } static int ComputeSize(int numBones, int numConst, int numKeys, bool useKeyFrames) {} - unsigned short GetNumFrames() const {} + unsigned short GetNumFrames() const { + if (!mTimes) { + return mNumKeys; + } else { + return mTimes[mNumKeys - 2] + 1; + } + } - void GetArrays(DofInfo *&dofInfo, short *&dataBuf) {} + void GetArrays(DofInfo *&dofInfo, short *&dataBuf) { + dofInfo = GetDofInfo(); + dataBuf = GetData(); + } - DofInfo *GetDofInfo() {} + DofInfo *GetDofInfo() { + return reinterpret_cast(&this[1]); + } - short *GetData() {} + short *GetData() { + return reinterpret_cast(&reinterpret_cast(GetDofInfo())[mNumBones * sizeof(DofInfo)]); + } - short *GetFrameData(short *dataBuf, int frameIdx) {} + short *GetFrameData(short *dataBuf, int frameIdx) { + return &dataBuf[frameIdx * 3 * mNumBones]; + } - float *GetConstData(short *dataBuf) {} + float *GetConstData(short *dataBuf) { + return reinterpret_cast(AlignSize4(reinterpret_cast(&dataBuf[mNumKeys * 3 * mNumBones]))); + } - unsigned short *GetConstBoneIdx() {} + unsigned short *GetConstBoneIdx() { + return &mDofIdxs[mNumBones]; + } - void UnQuantize(const DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) const {} + void UnQuantize(const DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) const { + result.x = dofInfo.mRange[0] * frameBuf[0]; + result.y = dofInfo.mRange[1] * frameBuf[1]; + result.z = dofInfo.mRange[2] * frameBuf[2]; + } static int GetFnOffset() { return 24; From 198fdfb81c6fc1c103d8fa22cc43c35463251f3d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 20:51:11 +0100 Subject: [PATCH 038/372] 43.4%: inline zEagl4Anim helper locals --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 28 +++++++-------- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 34 +++++++++---------- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 4 +-- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 6 ++-- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 0696755f3..bbe944eb4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -15,37 +15,37 @@ static const float kRangeScale15Bit = 6.1037019e-5f; static const float kRangeScale8Bit = 7.8431377e-3f; static const float kRangeScale7Bit = 1.5748032e-2f; -static int GetFrameDeltaSize(const DeltaQ *deltaQ) { +static inline int GetFrameDeltaSize(const DeltaQ *deltaQ) { return deltaQ->mNumBones * sizeof(DeltaQDelta); } -static int GetBinSize(const DeltaQ *deltaQ) { +static inline int GetBinSize(const DeltaQ *deltaQ) { return static_cast(AlignSize2((deltaQ->mNumBones * sizeof(DeltaQPhysical)) + ((deltaQ->GetBinLength() - 1) * GetFrameDeltaSize(deltaQ)))); } -static DeltaQMinRange *GetMinRanges(DeltaQ *deltaQ) { +static inline DeltaQMinRange *GetMinRanges(DeltaQ *deltaQ) { return deltaQ->GetMinRange(); } -static unsigned char *GetBinStart(DeltaQ *deltaQ) { +static inline unsigned char *GetBinStart(DeltaQ *deltaQ) { return &reinterpret_cast(GetMinRanges(deltaQ))[deltaQ->mNumBones * sizeof(DeltaQMinRange)]; } -static unsigned char *GetBin(DeltaQ *deltaQ, int binIdx) { +static inline unsigned char *GetBin(DeltaQ *deltaQ, int binIdx) { return &GetBinStart(deltaQ)[binIdx * GetBinSize(deltaQ)]; } -static DeltaQPhysical *GetPhysical(unsigned char *binData) { +static inline DeltaQPhysical *GetPhysical(unsigned char *binData) { return reinterpret_cast(binData); } -static DeltaQDelta *GetDelta(DeltaQ *deltaQ, unsigned char *binData, int deltaIdx) { +static inline DeltaQDelta *GetDelta(DeltaQ *deltaQ, unsigned char *binData, int deltaIdx) { return reinterpret_cast(&binData[deltaQ->mNumBones * sizeof(DeltaQPhysical) + (deltaIdx * GetFrameDeltaSize(deltaQ))]); } -static unsigned char *GetConstBoneIdx(DeltaQ *deltaQ) { +static inline unsigned char *GetConstBoneIdx(DeltaQ *deltaQ) { const int binSize = GetBinSize(deltaQ); int numBins = deltaQ->mNumKeys >> deltaQ->GetBinLengthPower(); unsigned char *s = &GetBin(deltaQ, 0)[binSize * numBins]; @@ -63,11 +63,11 @@ static unsigned char *GetConstBoneIdx(DeltaQ *deltaQ) { return s; } -static DeltaQPhysical *GetConstPhysical(DeltaQ *deltaQ) { +static inline DeltaQPhysical *GetConstPhysical(DeltaQ *deltaQ) { return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx(deltaQ)[deltaQ->mNumConstBones]))); } -static void RecoverW(int signBit, UMath::Vector4 &q) { +static inline void RecoverW(int signBit, UMath::Vector4 &q) { float ndotn = q.x * q.x + q.y * q.y + q.z * q.z; if (ndotn <= kFloatOne) { @@ -85,14 +85,14 @@ static void RecoverW(int signBit, UMath::Vector4 &q) { } } -static void DecodePhysical(const DeltaQPhysical &physical, UMath::Vector4 &q) { +static inline void DecodePhysical(const DeltaQPhysical &physical, UMath::Vector4 &q) { q.x = physical.mX * kRangeScale15Bit - kFloatOne; q.y = physical.mY * kRangeScale16Bit - kFloatOne; q.z = physical.mZ * kRangeScale16Bit - kFloatOne; RecoverW(physical.mW, q); } -static void DecodeMinRange(const DeltaQMinRange &minRange, DeltaQMinRangef &minRangef) { +static inline void DecodeMinRange(const DeltaQMinRange &minRange, DeltaQMinRangef &minRangef) { minRangef.mMin.x = minRange.mMin[0] * kRangeScale16Bit - kFloatOne; minRangef.mMin.y = minRange.mMin[1] * kRangeScale16Bit - kFloatOne; minRangef.mMin.z = minRange.mMin[2] * kRangeScale16Bit - kFloatOne; @@ -102,7 +102,7 @@ static void DecodeMinRange(const DeltaQMinRange &minRange, DeltaQMinRangef &minR minRangef.mRange.z = 2.0f * (minRange.mRange[2] * kRangeScale16Bit) * kRangeScale8Bit; } -static void DecodeDelta(const DeltaQMinRange &minRange, const DeltaQDelta &delta, UMath::Vector4 &q) { +static inline void DecodeDelta(const DeltaQMinRange &minRange, const DeltaQDelta &delta, UMath::Vector4 &q) { DeltaQMinRangef minRangef; DecodeMinRange(minRange, minRangef); @@ -113,7 +113,7 @@ static void DecodeDelta(const DeltaQMinRange &minRange, const DeltaQDelta &delta RecoverW(delta.mW, q); } -static float *GetOutputQuat(float *sqt, unsigned char boneIdx) { +static inline float *GetOutputQuat(float *sqt, unsigned char boneIdx) { return &sqt[boneIdx * 12 + 4]; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 84deebad9..cc573edab 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -17,37 +17,37 @@ static const float kSingleQRangeScale16Bit = 3.0518044e-5f; static const float kSingleQRangeScale8Bit = 7.8431377e-3f; static const float kSingleQRangeScale4Bit = 0.13333334f; -static int GetSingleQFrameDeltaSize(const DeltaSingleQ *deltaQ) { +static inline int GetSingleQFrameDeltaSize(const DeltaSingleQ *deltaQ) { return deltaQ->mNumBones * sizeof(DeltaSingleQDelta); } -static int GetSingleQBinSize(const DeltaSingleQ *deltaQ) { +static inline int GetSingleQBinSize(const DeltaSingleQ *deltaQ) { return static_cast(AlignSize2((deltaQ->mNumBones * sizeof(DeltaSingleQPhysical)) + ((deltaQ->GetBinLength() - 1) * GetSingleQFrameDeltaSize(deltaQ)))); } -static DeltaSingleQMinRange *GetSingleQMinRanges(DeltaSingleQ *deltaQ) { +static inline DeltaSingleQMinRange *GetSingleQMinRanges(DeltaSingleQ *deltaQ) { return deltaQ->GetMinRange(); } -static unsigned char *GetSingleQBinStart(DeltaSingleQ *deltaQ) { +static inline unsigned char *GetSingleQBinStart(DeltaSingleQ *deltaQ) { return &reinterpret_cast(GetSingleQMinRanges(deltaQ))[deltaQ->mNumBones * sizeof(DeltaSingleQMinRange)]; } -static unsigned char *GetSingleQBin(DeltaSingleQ *deltaQ, int binIdx) { +static inline unsigned char *GetSingleQBin(DeltaSingleQ *deltaQ, int binIdx) { return &GetSingleQBinStart(deltaQ)[binIdx * GetSingleQBinSize(deltaQ)]; } -static DeltaSingleQPhysical *GetSingleQPhysical(unsigned char *binData) { +static inline DeltaSingleQPhysical *GetSingleQPhysical(unsigned char *binData) { return reinterpret_cast(binData); } -static DeltaSingleQDelta *GetSingleQDelta(DeltaSingleQ *deltaQ, unsigned char *binData, int deltaIdx) { +static inline DeltaSingleQDelta *GetSingleQDelta(DeltaSingleQ *deltaQ, unsigned char *binData, int deltaIdx) { return reinterpret_cast(&binData[deltaQ->mNumBones * sizeof(DeltaSingleQPhysical) + (deltaIdx * GetSingleQFrameDeltaSize(deltaQ))]); } -static void NormalizeSingleQQuat(UMath::Vector4 &q) { +static inline void NormalizeSingleQQuat(UMath::Vector4 &q) { float s = kSingleQFloatOne / FastSqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); q.x *= s; @@ -56,7 +56,7 @@ static void NormalizeSingleQQuat(UMath::Vector4 &q) { q.w *= s; } -static void SingleQEulToQuat(const float *eulData, float *quatData) { +static inline void SingleQEulToQuat(const float *eulData, float *quatData) { float ti = eulData[0] * kSingleQHalf; float tj = eulData[1] * kSingleQHalf; float th = eulData[2] * kSingleQHalf; @@ -77,7 +77,7 @@ static void SingleQEulToQuat(const float *eulData, float *quatData) { quatData[3] = cc * ch + ss * sh; } -static void SingleQQuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { +static inline void SingleQQuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { float awby = a.w * b.y; float axbw = a.x * b.w; float awbw = a.w * b.w; @@ -89,21 +89,21 @@ static void SingleQQuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 & result.w = -naxby * c.z + awbw * c.w; } -static void SingleQQuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { +static inline void SingleQQuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { result.x = a.w * b.x + a.x * b.w; result.y = a.w * b.y + a.x * b.z; result.z = -(a.x * b.y) + a.w * b.z; result.w = -(a.x * b.x) + a.w * b.w; } -static void SingleQQuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { +static inline void SingleQQuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { result.x = a.x * b.w - a.y * b.z; result.y = a.x * b.z + a.y * b.w; result.z = a.z * b.w + a.w * b.z; result.w = -(a.z * b.z) + a.w * b.w; } -static void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSingleQMinRangef &minRangef) { +static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSingleQMinRangef &minRangef) { minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit - kSingleQPi; minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit - kSingleQPi; minRangef.mMin[0] = minRange.mMin[0] * kSingleQRangeScale16Bit - kSingleQFloatOne; @@ -113,7 +113,7 @@ static void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSin minRangef.mIndex = static_cast(minRange.mIndex); } -static void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { +static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { q.x = kSingleQFloatZero; q.y = kSingleQFloatZero; q.z = kSingleQFloatZero; @@ -128,7 +128,7 @@ static void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int inde } } -static void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, const DeltaSingleQDelta &delta, UMath::Vector4 &q) { +static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, const DeltaSingleQDelta &delta, UMath::Vector4 &q) { DeltaSingleQMinRangef minRangef; float v = minRangef.mMin[0]; float w = minRangef.mMin[1]; @@ -152,7 +152,7 @@ static void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, const Delta } } -static void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, +static inline void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, const UMath::Vector4 &post, UMath::Vector4 &result) { if (index == 1) { SingleQQuatMultXxYxZ(pre, mid, post, result); @@ -163,7 +163,7 @@ static void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, } } -static float *GetSingleQOutputQuat(float *sqt, unsigned char boneIdx) { +static inline float *GetSingleQOutputQuat(float *sqt, unsigned char boneIdx) { return &sqt[boneIdx * 12 + 4]; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 62e6b2880..d31762514 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -6,11 +6,11 @@ namespace EAGL4Anim { namespace { -static unsigned char GetStatelessF3BoneIndex(unsigned short dofIdx) { +static inline unsigned char GetStatelessF3BoneIndex(unsigned short dofIdx) { return static_cast(dofIdx / 12); } -static void UnquantizeStatelessF3(const StatelessF3::DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) { +static inline void UnquantizeStatelessF3(const StatelessF3::DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) { result.x = dofInfo.mRange[0] * frameBuf[0]; result.y = dofInfo.mRange[1] * frameBuf[1]; result.z = dofInfo.mRange[2] * frameBuf[2]; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index af32a993d..3d16902a4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -7,7 +7,7 @@ namespace EAGL4Anim { namespace { -static float UncompressStatelessQValue(unsigned short value) { +static inline float UncompressStatelessQValue(unsigned short value) { union { unsigned int u; float f; @@ -17,11 +17,11 @@ static float UncompressStatelessQValue(unsigned short value) { return bits.f; } -static float *GetStatelessQOutput(float *sqt, unsigned char boneIdx) { +static inline float *GetStatelessQOutput(float *sqt, unsigned char boneIdx) { return &sqt[boneIdx * 12 + 4]; } -static void LoadStatelessQ(unsigned short *frameData, UMath::Vector4 &q) { +static inline void LoadStatelessQ(unsigned short *frameData, UMath::Vector4 &q) { q.x = UncompressStatelessQValue(frameData[0]); q.y = UncompressStatelessQValue(frameData[1]); q.z = UncompressStatelessQValue(frameData[2]); From 3536d8c62f38d607ea799757755e4bafd5724eb0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 20:56:43 +0100 Subject: [PATCH 039/372] 45.8%: add FnPoseBlender first pass --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 168 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h | 4 +- 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index e69de29bb..2325bbc0c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -0,0 +1,168 @@ +#include "FnPoseBlender.h" + +#include "AnimUtil.h" + + +namespace EAGL4Anim { + +namespace { + +static inline void ApplyAlignedRootTransform(EAGL4::Transform &tmpTransform, const EAGL4::Transform &alignMatrix, + float *pose, int boneIdx) { + UMath::Vector4 quat; + UMath::Vector4 trans; + float *bonePose = &pose[boneIdx * 12]; + + tmpTransform.BuildSQT(bonePose[0], bonePose[1], bonePose[2], bonePose[4], bonePose[5], bonePose[6], bonePose[7], + bonePose[8], bonePose[9], bonePose[10]); + tmpTransform.PostMult(alignMatrix); + tmpTransform.ExtractQuatTrans(&quat, &trans); + + bonePose[4] = reinterpret_cast(&quat)[0]; + bonePose[5] = reinterpret_cast(&quat)[1]; + bonePose[6] = reinterpret_cast(&quat)[2]; + bonePose[7] = reinterpret_cast(&quat)[3]; + bonePose[8] = reinterpret_cast(&trans)[0]; + bonePose[9] = reinterpret_cast(&trans)[1]; + bonePose[10] = reinterpret_cast(&trans)[2]; +} + +static inline void BlendRootTranslation(float w, const float *pose0, const float *pose1, float *out, int boneIdx) { + int transIdx = boneIdx * 12 + 8; + + out[transIdx + 0] = pose0[transIdx + 0] + w * (pose1[transIdx + 0] - pose0[transIdx + 0]); + out[transIdx + 1] = pose0[transIdx + 1] + w * (pose1[transIdx + 1] - pose0[transIdx + 1]); + out[transIdx + 2] = pose0[transIdx + 2] + w * (pose1[transIdx + 2] - pose0[transIdx + 2]); +} + +}; // namespace + +FnPoseBlender::~FnPoseBlender() {} + +void FnPoseBlender::operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); +} + +void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float *pose1, float *result, + const BoneMask *boneMask) { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask && !boneMask->GetBone(boneIdx)) { + continue; + } + + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); + result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); + result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); + result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); + } +} + +bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask *boneMask) { + if (currentTime <= mStartTransTime) { + return mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], sqtBuffer, boneMask); + } + + if (currentTime >= mEndTransTime) { + bool result = mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], sqtBuffer, boneMask); + + if (result && mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { + EAGL4::Transform rootTransform; + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, sqtBuffer, mAlignRootBoneIdx); + } + + return result; + } + + float w = (currentTime - mStartTransTime) / mDuration; + + if (mResetBuffers[0]) { + mpSkel->GetStillPose(mPose[0], boneMask); + } + + if (mResetBuffers[1]) { + mpSkel->GetStillPose(mPose[1], boneMask); + } + + if (!mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], mPose[0], boneMask)) { + return false; + } + + if (!mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], sqtBuffer, boneMask)) { + return false; + } + + if (mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { + EAGL4::Transform rootTransform; + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, sqtBuffer, mAlignRootBoneIdx); + } + + int numBones = mpSkel->GetNumBones(); + int transBoneIdx = mAlignRootBoneIdx < 0 ? 0 : mAlignRootBoneIdx; + + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask && !boneMask->GetBone(boneIdx)) { + continue; + } + + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &sqtBuffer[poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + + if (!boneMask || boneMask->GetBone(transBoneIdx)) { + BlendRootTranslation(w, mPose[0], sqtBuffer, sqtBuffer, transBoneIdx); + } + + return true; +} + +void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPose) { + if (currentTime <= mStartTransTime) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], outputPose); + return; + } + + if (currentTime >= mEndTransTime) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], outputPose); + + if (mAlignRootBoneIdx >= 0) { + EAGL4::Transform rootTransform; + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, outputPose, mAlignRootBoneIdx); + } + + return; + } + + float w = (currentTime - mStartTransTime) / mDuration; + + if (mResetBuffers[0]) { + mpSkel->GetStillPose(mPose[0], 0); + } + + if (mResetBuffers[1]) { + mpSkel->GetStillPose(mPose[1], 0); + } + + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], mPose[0]); + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], outputPose); + + if (mAlignRootBoneIdx >= 0) { + EAGL4::Transform rootTransform; + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, outputPose, mAlignRootBoneIdx); + } + + int numBones = mpSkel->GetNumBones(); + int transBoneIdx = mAlignRootBoneIdx < 0 ? 0 : mAlignRootBoneIdx; + + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &outputPose[poseIdx + 4], &outputPose[poseIdx + 4]); + } + + BlendRootTranslation(w, mPose[0], outputPose, outputPose, transBoneIdx); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h index 82406c4f9..76db1da86 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h @@ -22,13 +22,13 @@ class FnPoseBlender : public FnAnim { } // Overrides: FnAnimSuper - ~FnPoseBlender() override {} + ~FnPoseBlender() override; // void *operator new(size_t size) {} // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size); // void *operator new[](size_t size) {} From bc8871587046369bfc57e64f80f832899ccac80f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:00:40 +0100 Subject: [PATCH 040/372] 47.1%: add transform helpers for pose blending --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 2325bbc0c..1afc1d6b2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -3,6 +3,136 @@ #include "AnimUtil.h" +namespace EAGL4 { + +void MultMatrix(const UMath::Matrix4 *pm1, const UMath::Matrix4 *pm2, UMath::Matrix4 *presult) { + const float *m1 = pm1->GetElements(); + const float *m2 = pm2->GetElements(); + float *result = presult->GetElements(); + + result[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + m1[3] * m2[12]; + result[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + m1[3] * m2[13]; + result[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + m1[3] * m2[14]; + result[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3] * m2[15]; + result[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + m1[7] * m2[12]; + result[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + m1[7] * m2[13]; + result[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + m1[7] * m2[14]; + result[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7] * m2[15]; + result[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + m1[11] * m2[12]; + result[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + m1[11] * m2[13]; + result[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + m1[11] * m2[14]; + result[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11] * m2[15]; + result[12] = m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + m1[15] * m2[12]; + result[13] = m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + m1[15] * m2[13]; + result[14] = m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + m1[15] * m2[14]; + result[15] = m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + m1[15] * m2[15]; +} + +void Transform::PostMult(const Transform &second, Transform *pOutput) const { + MultMatrix(&m, &second.m, &pOutput->m); +} + +void Transform::PostMult(const Transform &second) { + Transform result; + + MultMatrix(&m, &second.m, &result.m); + m = result.m; +} + +void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTrans) const { + const float *mat = m.GetElements(); + float xx = mat[0]; + float yy = mat[5]; + float zz = mat[10]; + float xy = mat[1]; + float xz = mat[2]; + float yx = mat[4]; + float yz = mat[6]; + float zx = mat[8]; + float zy = mat[9]; + float trace = xx + yy + zz; + + if (trace > 0.0f) { + float s = EAGL4Anim::FastSqrt(trace + 1.0f); + float invS = 0.5f / s; + + retQuat->w = s * 0.5f; + retQuat->z = (xy - yx) * invS; + retQuat->x = (yz - zy) * invS; + retQuat->y = (zx - xz) * invS; + } else { + float maxDiag = yy; + + if (xx >= yy) { + maxDiag = xx; + } + + if (zz > maxDiag) { + float s = EAGL4Anim::FastSqrt((zz - (xx + yy)) + 1.0f); + + retQuat->z = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (xy - yx) * s; + retQuat->y = (zy + yz) * s; + retQuat->x = (zx + xz) * s; + } else if (xx >= yy) { + float s = EAGL4Anim::FastSqrt((xx - (yy + zz)) + 1.0f); + + retQuat->x = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (yz - zy) * s; + retQuat->y = (xy + yx) * s; + retQuat->z = (xz + zx) * s; + } else { + float s = EAGL4Anim::FastSqrt((yy - (zz + xx)) + 1.0f); + + retQuat->y = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (zx - xz) * s; + retQuat->x = (yx + xy) * s; + retQuat->z = (yz + zy) * s; + } + } + + retTrans->x = mat[12]; + retTrans->y = mat[13]; + retTrans->z = mat[14]; + retTrans->w = mat[15]; +} + +void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float qz, float qw, float tx, float ty, float tz) { + float *mat = m.GetElements(); + float qy2 = qy + qy; + float qz2 = qz + qz; + float qx2qx = qx * (qx + qx); + float qw2qx = qw * (qx + qx); + + mat[12] = tx; + mat[13] = ty; + mat[15] = 1.0f; + mat[11] = 0.0f; + mat[3] = 0.0f; + mat[7] = 0.0f; + mat[14] = tz; + mat[2] = sx * (qw * qz2 - qw * qy2); + mat[6] = sy * (qy * qz2 + qw2qx); + mat[10] = sz * (1.0f - (qx2qx + qy * qy2)); + mat[0] = sx * (1.0f - (qy * qy2 + qz * qz2)); + mat[4] = sy * (qw * qy2 - qz * qz2); + mat[8] = sz * (qw * qz2 + qz * qy2); + mat[1] = sx * (qw * qy2 + qz * qz2); + mat[5] = sy * (1.0f - (qx2qx + qz * qz2)); + mat[9] = sz * (qy * qz2 - qw2qx); +} + +}; // namespace EAGL4 + namespace EAGL4Anim { namespace { From a5a54e75bdc9c777cfa64be45c3fb4666c4f6b17 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:05:22 +0100 Subject: [PATCH 041/372] 49.4%: add FnDeltaQFast first pass --- src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h | 39 +- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 538 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h | 24 +- 3 files changed, 569 insertions(+), 32 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h index 04999bb76..ac72a1499 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h @@ -78,11 +78,15 @@ struct DeltaQFast : public AnimMemoryMap { return result; } - void GetArrays(DeltaQFastMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQFastPhysical *&constPhysical) {} + void GetArrays(DeltaQFastMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQFastPhysical *&constPhysical) { + minRanges = GetMinRange(); + binStart = GetBin(0); + constBoneIndices = reinterpret_cast(GetConstBoneIdx()); + constPhysical = reinterpret_cast(GetConstPhysical()); + } int GetBinSize() const { - // TODO - return AlignSize2(3 * ((GetBinLength() - 1) * mNumBones)); + return AlignSize2(mNumBones * (6 + ((GetBinLength() - 1) * 3))); } DeltaQFastMinRange *GetMinRange() { @@ -100,26 +104,29 @@ struct DeltaQFast : public AnimMemoryMap { return reinterpret_cast(binData); } - DeltaQFastDelta *GetDelta(unsigned char *binData, int deltaIdx) {} + DeltaQFastDelta *GetDelta(unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[mNumBones * 6 + deltaIdx * mNumBones * 3]); + } unsigned short *GetConstBoneIdx() { - unsigned int binLen = GetBinLength(); const int binSize = GetBinSize(); - // TODO it's out of line - // int numBins = mNumFrames >> GetBinLengthPower(); // r8 - // // get to the end of the bins - // unsigned char *s = &GetBin(0)[binSize * numBins]; // r11 - // int r = mNumFrames & GetBinLengthModMask(); // r31 + int numFrames = GetNumFrames(); + int numBins = numFrames >> GetBinLengthPower(); + unsigned char *s = &GetBin(0)[binSize * numBins]; + int r = numFrames & GetBinLengthModMask(); - // if (r > 0) { - // s = reinterpret_cast(AlignSize2((intptr_t)s + mNumBones * 2 + (r - 1) * GetFrameDeltaSize())); - // } + if (r > 0) { + s = reinterpret_cast(AlignSize2(reinterpret_cast(s + mNumBones * 6 + ((r - 1) * mNumBones * 3)))); + } + if (mNumBones == 0) { + s = reinterpret_cast(AlignSize2(reinterpret_cast(s))); + } - return reinterpret_cast(nullptr); + return reinterpret_cast(s); } - float *GetConstPhysical() { - // return reinterpret_cast(AlignSize4(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); + void *GetConstPhysical() { + return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); } unsigned short mNumKeys; // offset 0x4, size 0x2 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index e69de29bb..e64885808 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -0,0 +1,538 @@ +#include "FnDeltaQFast.h" + +#include "AnimTypeId.h" +#include "AnimUtil.h" + + +void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { + float awby = a.w * b.y; + float axbw = a.x * b.w; + float awbw = a.w * b.w; + float naxby = -(a.x * b.y); + + result.x = axbw * c.w - awby * c.z; + result.y = axbw * c.z + awby * c.w; + result.z = naxby * c.w + awbw * c.z; + result.w = -naxby * c.z + awbw * c.w; +} + +void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.w * b.x + a.x * b.w; + result.y = a.w * b.y + a.x * b.z; + result.z = -(a.x * b.y) + a.w * b.z; + result.w = -(a.x * b.x) + a.w * b.w; +} + +void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w - a.y * b.z; + result.y = a.x * b.z + a.y * b.w; + result.z = a.z * b.w + a.w * b.z; + result.w = -(a.z * b.z) + a.w * b.w; +} + +namespace EAGL4Anim { + +namespace { + +static const float kQFastMinScale16 = 3.0518044e-5f; +static const float kQFastDeltaScale6 = 1.5873017e-2f; +static const float kQFastPhysicalScale12 = 4.8840049e-4f; +static const float kQFastPhysicalBias12 = 1.0002443f; + +static inline float *GetQFastOutputQuat(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +static inline unsigned char *GetQFastBin(unsigned char *bins, int binSize, int binIdx) { + return &bins[binIdx * binSize]; +} + +static inline DeltaQFastPhysical *GetQFastPhysical(unsigned char *binData) { + return reinterpret_cast(binData); +} + +static inline unsigned char *GetQFastDeltaData(DeltaQFast *deltaQ, unsigned char *binData, int deltaIdx) { + return &binData[deltaQ->mNumBones * 6 + deltaIdx * deltaQ->mNumBones * 3]; +} + +static inline void DecodeQFastPhysical(const DeltaQFastPhysical &physical, UMath::Vector4 &q) { + unsigned short xBits = physical.mX; + unsigned short yBits = physical.mY; + unsigned short zBits = physical.mZ; + unsigned short wBits = static_cast((physical.mW0 << 8) | (physical.mW1 << 4) | physical.mW2); + + q.x = xBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; + q.y = yBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; + q.z = zBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; + q.w = wBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; +} + +static inline void DecodeQFastDelta(const DeltaQFastMinRangef &minRangef, const unsigned char *deltaData, UMath::Vector4 &q) { + unsigned int xBits = deltaData[0] >> 2; + unsigned int yBits = deltaData[1] >> 2; + unsigned int zBits = deltaData[2] >> 2; + unsigned int wBits = ((deltaData[0] & 3) << 4) | ((deltaData[1] & 3) << 2) | (deltaData[2] & 3); + + q.x = minRangef.mMin.x + minRangef.mRange.x * (xBits * kQFastDeltaScale6); + q.y = minRangef.mMin.y + minRangef.mRange.y * (yBits * kQFastDeltaScale6); + q.z = minRangef.mMin.z + minRangef.mRange.z * (zBits * kQFastDeltaScale6); + q.w = minRangef.mMin.w + minRangef.mRange.w * (wBits * kQFastDeltaScale6); +} + +static inline int FindQFastFloorKey(int prevKey, DeltaQFast *deltaQ, float currTime) { + int floorTime = FloatToInt(currTime); + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + return 0; + } + if (floorTime >= deltaQ->mNumKeys) { + return deltaQ->mNumKeys - 1; + } + return floorTime; + } + if (floorTime < deltaQ->mTimes[0]) { + return 0; + } + + int timeIndex = prevKey < 1 ? 0 : prevKey - 1; + + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + return timeIndex + 1; +} + +static inline float ComputeQFastBlendT(DeltaQFast *deltaQ, int floorKey, int ceilKey, float currTime) { + if (ceilKey <= floorKey) { + return 0.0f; + } + if (!deltaQ->mTimes) { + return currTime - static_cast(floorKey); + } + + float floorTime = static_cast(deltaQ->mTimes[floorKey - 1]); + float ceilTime = static_cast(deltaQ->mTimes[ceilKey - 1]); + + if (floorKey == 0) { + floorTime = 0.0f; + } + if (ceilTime == floorTime) { + return 0.0f; + } + + return (currTime - floorTime) / (ceilTime - floorTime); +} + +} // namespace + +FnDeltaQFast::FnDeltaQFast() + : mMinRangesf(nullptr), // + mBins(nullptr), // + mBinSize(-1), // + mPrevKey(-1), // + mPrevQBlock(nullptr), // + mPrevQs(nullptr), // + mNextKey(-1), // + mNextQBlock(nullptr), // + mNextQs(nullptr), // + mConstBoneIdxs(nullptr), // + mConstPhysical(nullptr), // + mBoneMask(nullptr) { + mType = AnimTypeId::ANIM_DELTAQFAST; +} + +FnDeltaQFast::~FnDeltaQFast() { + if (mPrevQBlock) { + MemoryPoolManager::DeleteBlock(mPrevQBlock); + } +} + +void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { + DeltaQFast *deltaQ = reinterpret_cast(anim); + int numBones = deltaQ->mNumBones; + + mpAnim = anim; + mBins = reinterpret_cast(deltaQ->GetMinRange()) + numBones * sizeof(DeltaQFastMinRange); + mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); + mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); + mBinSize = AlignSize2(numBones * (6 + ((deltaQ->GetBinLength() - 1) * 3))); + mPrevKey = -1; + mNextKey = -1; + mBoneMask = nullptr; + + if (numBones != 0) { + unsigned char *block = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * 0x40)); + DeltaQFastMinRange *minRange = deltaQ->GetMinRange(); + + mMinRangesf = reinterpret_cast(block); + mPrevQBlock = block + numBones * sizeof(DeltaQFastMinRangef); + mPrevQs = reinterpret_cast(mPrevQBlock); + mNextQBlock = reinterpret_cast(mPrevQs) + numBones * sizeof(*mPrevQs); + mNextQs = reinterpret_cast(mNextQBlock); + + for (int ibone = 0; ibone < numBones; ibone++) { + mMinRangesf[ibone].mMin.x = minRange[ibone].mMin[0] * kQFastMinScale16 - 1.0f; + mMinRangesf[ibone].mMin.y = minRange[ibone].mMin[1] * kQFastMinScale16 - 1.0f; + mMinRangesf[ibone].mMin.z = minRange[ibone].mMin[2] * kQFastMinScale16 - 1.0f; + mMinRangesf[ibone].mMin.w = minRange[ibone].mMin[3] * kQFastMinScale16 - 1.0f; + mMinRangesf[ibone].mRange.x = minRange[ibone].mRange[0] * kQFastMinScale16; + mMinRangesf[ibone].mRange.y = minRange[ibone].mRange[1] * kQFastMinScale16; + mMinRangesf[ibone].mRange.z = minRange[ibone].mRange[2] * kQFastMinScale16; + mMinRangesf[ibone].mRange.w = minRange[ibone].mRange[3] * kQFastMinScale16; + } + } +} + +bool FnDeltaQFast::GetLength(float &timeLength) const { + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + + timeLength = static_cast(deltaQ->GetNumFrames()); + return true; +} + +void FnDeltaQFast::Eval(float prevTime, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +void FnDeltaQFast::InitBuffers() { + if (mpAnim) { + SetAnimMemoryMap(mpAnim); + } +} + +void FnDeltaQFast::AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) { + unsigned char *binData = reinterpret_cast(floorPhys); + + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + prevQs[ibone].x += delta.x; + prevQs[ibone].y += delta.y; + prevQs[ibone].z += delta.z; + prevQs[ibone].w += delta.w; + } + } +} + +void FnDeltaQFast::SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) { + unsigned char *binData = reinterpret_cast(floorPhys); + + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + prevQs[ibone].x -= delta.x; + prevQs[ibone].y -= delta.y; + prevQs[ibone].z -= delta.z; + prevQs[ibone].w -= delta.w; + } + } +} + +void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx, int floorDeltaIdx) { + if (ceilKey == mNextKey) { + return; + } + + int ceilBinIdx = ceilKey >> deltaQ->GetBinLengthPower(); + unsigned char *binData = GetQFastBin(mBins, mBinSize, ceilBinIdx); + + if (ceilBinIdx == floorBinIdx) { + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + mNextQs[ibone].x = mPrevQs[ibone].x + delta.x; + mNextQs[ibone].y = mPrevQs[ibone].y + delta.y; + mNextQs[ibone].z = mPrevQs[ibone].z + delta.z; + mNextQs[ibone].w = mPrevQs[ibone].w + delta.w; + } + } else { + DeltaQFastPhysical *physical = GetQFastPhysical(binData); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DecodeQFastPhysical(physical[ibone], mNextQs[ibone]); + } + } + + mNextKey = ceilKey; +} + +void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs, + const BoneMask *boneMask) { + unsigned char *binData = reinterpret_cast(floorPhys); + + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + prevQs[ibone].x += delta.x; + prevQs[ibone].y += delta.y; + prevQs[ibone].z += delta.z; + prevQs[ibone].w += delta.w; + } + } +} + +void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs, + const BoneMask *boneMask) { + unsigned char *binData = reinterpret_cast(floorPhys); + + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + prevQs[ibone].x -= delta.x; + prevQs[ibone].y -= delta.y; + prevQs[ibone].z -= delta.z; + prevQs[ibone].w -= delta.w; + } + } +} + +void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx, int floorDeltaIdx, const BoneMask *boneMask) { + if (ceilKey == mNextKey) { + return; + } + + int ceilBinIdx = ceilKey >> deltaQ->GetBinLengthPower(); + unsigned char *binData = GetQFastBin(mBins, mBinSize, ceilBinIdx); + + if (ceilBinIdx == floorBinIdx) { + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + mNextQs[ibone].x = mPrevQs[ibone].x + delta.x; + mNextQs[ibone].y = mPrevQs[ibone].y + delta.y; + mNextQs[ibone].z = mPrevQs[ibone].z + delta.z; + mNextQs[ibone].w = mPrevQs[ibone].w + delta.w; + } + } else { + DeltaQFastPhysical *physical = GetQFastPhysical(binData); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + DecodeQFastPhysical(physical[ibone], mNextQs[ibone]); + } + } + + mNextKey = ceilKey; +} + +bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (!mpAnim) { + return false; + } + if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { + InitBuffers(); + } + if (boneMask) { + return EvalSQTMask(currTime, sqt, boneMask); + } + if (mBoneMask) { + mBoneMask = nullptr; + mPrevKey = -1; + mNextKey = -1; + } + + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); + int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); + int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); + int prevBinIdx = mPrevKey >> deltaQ->GetBinLengthPower(); + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + unsigned char *binData = GetQFastBin(mBins, mBinSize, floorBinIdx); + DeltaQFastPhysical *floorPhys = GetQFastPhysical(binData); + int prevDeltaIdx; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DecodeQFastPhysical(floorPhys[ibone], mPrevQs[ibone]); + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & deltaQ->GetBinLengthModMask(); + } + + if (prevDeltaIdx < floorDeltaIdx) { + AddDelta(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); + } else if (prevDeltaIdx > floorDeltaIdx) { + SubDelta(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); + } + + int ceilKey = floorKey + 1; + + if (ceilKey >= deltaQ->mNumKeys) { + ceilKey = floorKey; + } + + if (ceilKey > floorKey) { + UpdateNextQs(deltaQ, ceilKey, floorBinIdx, floorDeltaIdx); + float t = ComputeQFastBlendT(deltaQ, floorKey, ceilKey, currTime); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + FastQuatBlendF4(t, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&mNextQs[ibone]), + GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone])); + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + float *out = GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone]); + + out[0] = mPrevQs[ibone].x; + out[1] = mPrevQs[ibone].y; + out[2] = mPrevQs[ibone].z; + out[3] = mPrevQs[ibone].w; + } + } + + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + UMath::Vector4 q; + float *out = GetQFastOutputQuat(sqt, mConstBoneIdxs[ibone]); + + DecodeQFastPhysical(mConstPhysical[ibone], q); + out[0] = q.x; + out[1] = q.y; + out[2] = q.z; + out[3] = q.w; + } + + mPrevKey = floorKey; + return true; +} + +bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneMask) { + if (!mpAnim) { + return false; + } + if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { + InitBuffers(); + } + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + mPrevKey = -1; + mNextKey = -1; + } + + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); + int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); + int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); + int prevBinIdx = mPrevKey >> deltaQ->GetBinLengthPower(); + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + unsigned char *binData = GetQFastBin(mBins, mBinSize, floorBinIdx); + DeltaQFastPhysical *floorPhys = GetQFastPhysical(binData); + int prevDeltaIdx; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + DecodeQFastPhysical(floorPhys[ibone], mPrevQs[ibone]); + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & deltaQ->GetBinLengthModMask(); + } + + if (prevDeltaIdx < floorDeltaIdx) { + AddDeltaMask(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs, boneMask); + } else if (prevDeltaIdx > floorDeltaIdx) { + SubDeltaMask(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs, boneMask); + } + + int ceilKey = floorKey + 1; + + if (ceilKey >= deltaQ->mNumKeys) { + ceilKey = floorKey; + } + + if (ceilKey > floorKey) { + UpdateNextQsMask(deltaQ, ceilKey, floorBinIdx, floorDeltaIdx, boneMask); + float t = ComputeQFastBlendT(deltaQ, floorKey, ceilKey, currTime); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + FastQuatBlendF4(t, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&mNextQs[ibone]), + GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone])); + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + continue; + } + + float *out = GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone]); + + out[0] = mPrevQs[ibone].x; + out[1] = mPrevQs[ibone].y; + out[2] = mPrevQs[ibone].z; + out[3] = mPrevQs[ibone].w; + } + } + + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + if (!boneMask->GetBone(mConstBoneIdxs[ibone])) { + continue; + } + + UMath::Vector4 q; + float *out = GetQFastOutputQuat(sqt, mConstBoneIdxs[ibone]); + + DecodeQFastPhysical(mConstPhysical[ibone], q); + out[0] = q.x; + out[1] = q.y; + out[2] = q.z; + out[3] = q.w; + } + + mPrevKey = floorKey; + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h index eaeba781a..75d0cd29e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h @@ -37,34 +37,26 @@ class FnDeltaQFast : public FnAnimMemoryMap { FnDeltaQFast(); // Overrides: FnAnimSuper - ~FnDeltaQFast() override { - if (mPrevQBlock) { - MemoryPoolManager::DeleteBlock(mPrevQBlock); - } - } + ~FnDeltaQFast() override; // Overrides: FnAnimMemoryMap - void SetAnimMemoryMap(AnimMemoryMap *anim) override { - mpAnim = anim; - } + void SetAnimMemoryMap(AnimMemoryMap *anim) override; // Overrides: FnAnim - bool GetLength(float &timeLength) const override { - DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + bool GetLength(float &timeLength) const override; - timeLength = static_cast(deltaQ->GetNumFrames()); - return true; - } + // Overrides: FnAnim + void Eval(float prevTime, float currTime, float *sqt) override; // Overrides: FnAnim bool EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) override; protected: - void InitBuffers() {} + void InitBuffers(); - void AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) {} + void AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs); - void SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) {} + void SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs); void UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx, int floorDeltaIdx); From b17ff1ab008aed58385bf74217885118f812f5c8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:09:45 +0100 Subject: [PATCH 042/372] 50.4%: add pose wrapper first passes --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 138 ++++++++++++++++++ .../Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp | 15 ++ .../Indep/Src/EAGL4Anim/FnRawPoseChannel.h | 29 +++- src/Speed/Indep/Src/EAGL4Anim/PosePalette.h | 24 ++- 4 files changed, 193 insertions(+), 13 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index e69de29bb..c87f6b432 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -0,0 +1,138 @@ +#include "FnPoseAnim.h" + +#include "AnimTypeId.h" +#include "AnimUtil.h" +#include "PoseAnim.h" + + +namespace EAGL4Anim { + +FnPoseAnim::FnPoseAnim() : mPrevKey(0) { + mType = AnimTypeId::ANIM_POSEANIM; +} + +void FnPoseAnim::SetAnimMemoryMap(AnimMemoryMap *anim) { + mpAnim = anim; +} + +bool FnPoseAnim::GetLength(float &timeLength) const { + const PoseAnim *poseAnim = reinterpret_cast(mpAnim); + + if (!poseAnim->mTimes) { + timeLength = static_cast(poseAnim->mNumKeys - 1); + } else { + timeLength = static_cast(poseAnim->mTimes[poseAnim->mNumKeys - 1]); + } + return true; +} + +bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, float *sqt) { + PoseAnim *poseAnim = reinterpret_cast(mpAnim); + unsigned short *times = poseAnim->mTimes; + unsigned int key = 0; + bool interp = false; + float t = 0.0f; + + if (currTime > static_cast(times[0])) { + unsigned int numKeys = poseAnim->mNumKeys; + + if (currTime >= static_cast(times[numKeys - 1])) { + key = numKeys - 1; + } else { + key = mPrevKey; + if (key != 0 && static_cast(currTime) < times[key]) { + do { + key--; + } while (key > 0 && static_cast(currTime) < times[key]); + } else if (static_cast(currTime) >= times[key + 1]) { + do { + key++; + } while (key < numKeys - 2 && static_cast(currTime) >= times[key + 1]); + } + + if (numKeys != 0) { + float floorTime = static_cast(times[key]); + + interp = currTime != floorTime; + if (interp) { + t = (currTime - floorTime) / (static_cast(times[key + 1]) - floorTime); + } + } + } + } + + mPrevKey = static_cast(key); + + const PosePalette *palette = paletteBank->GetPalettes()[poseAnim->mPaletteIndex]; + float *poseData = palette->GetPoseData(); + unsigned short *dofIndices = palette->GetDofIndices(); + unsigned int numQ = static_cast(palette->GetNumQs()); + unsigned int numT = static_cast(palette->GetNumTs()); + unsigned int poseIdx = poseAnim->mPoseIndices[key]; + + if (interp) { + int stride = (numQ + numT) * 4; + int src0 = (poseIdx + 1) * stride - 4; + int src1 = (poseAnim->mPoseIndices[key + 1] + 1) * stride - 4; + int transIdx = static_cast(numT) - 1; + + t = t * (3.0f - (t + t)) * t; + + while (transIdx >= 0) { + float *from = &poseData[src0]; + float *to = &poseData[src1]; + float *out = &sqt[dofIndices[numQ + transIdx] * 4]; + + out[0] = from[0] + (to[0] - from[0]) * t; + out[1] = from[1] + (to[1] - from[1]) * t; + out[2] = from[2] + (to[2] - from[2]) * t; + + src0 -= 4; + src1 -= 4; + transIdx--; + } + + int quatIdx = static_cast(numQ) - 1; + + while (quatIdx >= 0) { + FastQuatBlendF4(t, &poseData[src0], &poseData[src1], &sqt[dofIndices[quatIdx] * 4]); + src0 -= 4; + src1 -= 4; + quatIdx--; + } + } else { + int src = (poseIdx + 1) * (numQ + numT) * 4 - 4; + int transIdx = static_cast(numT) - 1; + + while (transIdx >= 0) { + float *from = &poseData[src]; + float *out = &sqt[dofIndices[numQ + transIdx] * 4]; + + out[0] = from[0]; + out[1] = from[1]; + out[2] = from[2]; + + src -= 4; + transIdx--; + } + + int quatIdx = static_cast(numQ) - 1; + + while (quatIdx >= 0) { + float *from = &poseData[src]; + float *out = &sqt[dofIndices[quatIdx] * 4]; + + out[0] = from[0]; + out[1] = from[1]; + out[2] = from[2]; + out[3] = from[3]; + + src -= 4; + quatIdx--; + } + } + + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp index e69de29bb..da50119d1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp @@ -0,0 +1,15 @@ +#include "FnRawPoseChannel.h" + + +namespace EAGL4Anim { + +void FnRawPoseChannel::Eval(float prevTime, float currentTime, float *outputPose) { + GetRawPoseChannel()->Eval(currentTime, outputPose, mInterp, nullptr); +} + +bool FnRawPoseChannel::EvalSQT(float currentTime, float *outputPose, const BoneMask *boneMask) { + GetRawPoseChannel()->Eval(currentTime, outputPose, mInterp, boneMask); + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h index 274d00576..feafed1fd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h @@ -36,20 +36,35 @@ class FnRawPoseChannel : public FnAnimMemoryMap { return ptr; } - RawPoseChannel *GetRawPoseChannel() {} + RawPoseChannel *GetRawPoseChannel() { + return reinterpret_cast(mpAnim); + } - const RawPoseChannel *GetRawPoseChannel() const {} + const RawPoseChannel *GetRawPoseChannel() const { + return reinterpret_cast(mpAnim); + } - void SetInterp(bool state) {} + void SetInterp(bool state) { + mInterp = state; + } - bool IsInterp() const {} + bool IsInterp() const { + return mInterp; + } // Overrides: FnAnim - bool GetLength(float &l) const override {} + bool GetLength(float &l) const override { + l = static_cast(GetRawPoseChannel()->GetNumFrames()); + return true; + } - int GetNumFrames() const {} + int GetNumFrames() const { + return GetRawPoseChannel()->GetNumFrames(); + } - int GetNumBones() const {} + int GetNumBones() const { + return GetRawPoseChannel()->GetNumBones(); + } // Overrides: FnAnim void Eval(float, float currentTime, float *outputPose) override; diff --git a/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h b/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h index 3b1ff9973..fedb873dc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h @@ -14,11 +14,17 @@ namespace EAGL4Anim { // total size: 0xC class PosePalette { public: - int GetNumPoses() const {} + int GetNumPoses() const { + return mNumPoses; + } - int GetNumQs() const {} + int GetNumQs() const { + return mNumQs; + } - int GetNumTs() const {} + int GetNumTs() const { + return mNumTs; + } void SetNumPoses(int n) {} @@ -26,9 +32,13 @@ class PosePalette { void SetNumTs(int n) {} - float *GetPoseData() const {} + float *GetPoseData() const { + return mPoseData; + } - unsigned short *GetDofIndices() const {} + unsigned short *GetDofIndices() const { + return mDofIndices; + } private: float *mPoseData; // offset 0x0, size 0x4 @@ -62,7 +72,9 @@ class PosePaletteBank { static int ComputeSize(int numPalettes) {} - const PosePalette **GetPalettes() const {} + const PosePalette *const *GetPalettes() const { + return reinterpret_cast(&this[1]); + } private: CheckSum mSkelCheckSum; // offset 0x0, size 0x2 From f9bb628eed3ea38cc13138d295ef2f14dcb557aa Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:12:12 +0100 Subject: [PATCH 043/372] 50.9%: add phase and event wrapper passes --- .../Indep/Src/EAGL4Anim/FnEventBlender.cpp | 27 +++++++ src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp | 71 +++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h | 68 +++++++++++++++--- 3 files changed, 155 insertions(+), 11 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index e69de29bb..7a082df7b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -0,0 +1,27 @@ +#include "FnEventBlender.h" + + +namespace EAGL4Anim { + +void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { + FnAnim *anim = mAnim[0]; + float timeOffset = mTimeOffset[0]; + + if (currentTime > mStartTransTime) { + if (currentTime >= mEndTransTime) { + anim = mAnim[1]; + timeOffset = mTimeOffset[1]; + } else if (mTriggerType == BOTH) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + anim = mAnim[1]; + timeOffset = mTimeOffset[1]; + } else if (mTriggerType == SECOND_ONLY) { + anim = mAnim[1]; + timeOffset = mTimeOffset[1]; + } + } + + anim->Eval(previousTime - timeOffset, currentTime - timeOffset, data); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp index e69de29bb..ec0b62fd0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp @@ -0,0 +1,71 @@ +#include "PhaseChan.h" + + +namespace EAGL4Anim { + +bool FnPhaseChan::GetLength(float &timeLength) const { + const PhaseChan *phaseChan = reinterpret_cast(mpAnim); + + timeLength = static_cast(phaseChan->mNumFrames - 1); + return true; +} + +void FnPhaseChan::SetAnimMemoryMap(AnimMemoryMap *anim) { + PhaseChan *phaseChan = reinterpret_cast(anim); + unsigned char flag = phaseChan->mFlag; + + mpAnim = anim; + mIdx = 0; + mCurrentFrame = phaseChan->mStartTime; + mRight = (flag & 1) == 0; + + if ((flag & 0x8) != 0) { + mSampleRate = 1; + } else if ((flag & 0x10) != 0) { + mSampleRate = 2; + } else if ((flag & 0x20) != 0) { + mSampleRate = 4; + } else if ((flag & 0x40) != 0) { + mSampleRate = 8; + } else { + mSampleRate = 1; + } +} + +void FnPhaseChan::Eval(float prevTime, float currTime, float *phaseValue) { + PhaseChan *phaseChan = reinterpret_cast(mpAnim); + int lastFrame = phaseChan->mNumFrames - 1; + + if (phaseChan->IsCycleAnim()) { + if (currTime < 0.0f) { + float cycleLength = static_cast(lastFrame); + + currTime = currTime - static_cast(static_cast(currTime / cycleLength) * lastFrame); + } else { + float cycleLength = static_cast(lastFrame); + + if (currTime > cycleLength) { + float wrapped = currTime - cycleLength; + + currTime = wrapped - static_cast(static_cast(wrapped / cycleLength) * lastFrame); + } + } + } + + int frame = static_cast(currTime) / static_cast(mSampleRate); + float t = (currTime - static_cast(frame * mSampleRate)) / static_cast(mSampleRate); + int numCycles = phaseChan->mNumCycles > 1 ? phaseChan->mNumCycles : 2; + float angle0 = static_cast(phaseChan->GetAngles()[numCycles + frame]) * 1.4117647f - 180.0f; + + if (frame < lastFrame / static_cast(mSampleRate) + 1) { + float angle1 = static_cast(phaseChan->GetAngles()[numCycles + frame + 1]) * 1.4117647f - 180.0f; + + *phaseValue = (1.0f - t) * angle0 + t * angle1; + } else { + float angle1 = static_cast(phaseChan->GetAngles()[numCycles + frame - 1]) * 1.4117647f - 180.0f; + + *phaseValue = (t + 1.0f) * angle0 - t * angle1; + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h index db8fd151a..d0b9c1974 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h @@ -41,33 +41,77 @@ struct PhaseChan : public AnimMemoryMap { PhaseChan() {} - bool IsInvalid() const {} + bool IsInvalid() const { + return (mFlag & 0x4) != 0; + } - void SetInvalid(bool s) {} + void SetInvalid(bool s) { + if (s) { + mFlag |= 0x4; + } else { + mFlag &= ~0x4; + } + } - bool StartWithRight() const {} + bool StartWithRight() const { + return (mFlag & 0x1) != 0; + } - void SetStartWithRight(bool s) {} + void SetStartWithRight(bool s) { + if (s) { + mFlag |= 0x1; + } else { + mFlag &= ~0x1; + } + } - void SetCycleAnim(bool s) {} + void SetCycleAnim(bool s) { + if (s) { + mFlag |= 0x2; + } else { + mFlag &= ~0x2; + } + } - bool IsCycleAnim() const {} + bool IsCycleAnim() const { + return (mFlag & 0x2) != 0; + } static int ComputeSize(int numCycles, int numFrames, int sampleRateFlag) {} int GetSize() const {} - unsigned char *GetAngles() {} + unsigned char *GetAngles() { + return &mCycles[mNumCycles > 1 ? mNumCycles : 2]; + } - const unsigned char *GetAngles() const {} + const unsigned char *GetAngles() const { + return &mCycles[mNumCycles > 1 ? mNumCycles : 2]; + } void SetAngle(int i, float angle) {} float GetAngle(int i) const {} - int GetAngleSampleRate() const {} + int GetAngleSampleRate() const { + if ((mFlag & 0x8) != 0) { + return 1; + } + if ((mFlag & 0x10) != 0) { + return 2; + } + if ((mFlag & 0x20) != 0) { + return 4; + } + if ((mFlag & 0x40) != 0) { + return 8; + } + return 1; + } - int GetNumAngles() const {} + int GetNumAngles() const { + return mNumFrames / GetAngleSampleRate() + 1; + } bool FindMatchTime(const MatchPhaseInput &input, float &time) const; @@ -116,7 +160,9 @@ class FnPhaseChan : public FnAnimMemoryMap { void SetAnimMemoryMap(AnimMemoryMap *anim) override; // Overrides: FnAnim - const PhaseChan *GetPhaseChan() override {} + const PhaseChan *GetPhaseChan() override { + return reinterpret_cast(mpAnim); + } // Members unsigned short mIdx; // offset 0x10, size 0x2 From 972d9b46d1c310ce055870b2f19af2b24fd11dd7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:14:52 +0100 Subject: [PATCH 044/372] 52.0%: add FnRunBlender first pass --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 147 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h | 15 +- src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h | 8 +- 3 files changed, 165 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index e69de29bb..c8e3bd997 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -0,0 +1,147 @@ +#include "FnRunBlender.h" + +#include "FnPoseBlender.h" +#include "MemoryPoolManager.h" +#include "ScratchBuffer.h" + + +namespace EAGL4Anim { + +FnRunBlender::FnRunBlender() + : mAnims(nullptr), // + mPhases(nullptr), // + mVels(nullptr), // + mWeight(0.0f), // + mNumAnims(0), // + mIdx(-100), // + mSkeleton(nullptr), // + mFreq(1.0f), // + mPrevTime(0.0f), // + mOffset(0.0f), // + mCycleIdx(-100), // + mInit(false), // + mSpeed(nullptr) { + mType = AnimTypeId::ANIM_RUNBLENDER; + mFnAnims[0] = nullptr; + mFnAnims[1] = nullptr; + mFnVelAnims[0] = nullptr; + mFnVelAnims[1] = nullptr; + mAlignFrame[0] = 0.0f; + mAlignFrame[1] = 0.0f; + mCycles[0] = 0.0f; + mCycles[1] = 0.0f; +} + +FnRunBlender::~FnRunBlender() { + if (mFnAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); + } + if (mFnAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); + } + if (mFnVelAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); + } + if (mFnVelAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); + } + if (mNumAnims) { + ScratchBuffer::GetScratchBuffer(0).FreeBuffer(); + } + if (mAnims) { + MemoryPoolManager::DeleteBlock(mAnims); + } + if (mPhases) { + MemoryPoolManager::DeleteBlock(const_cast(mPhases)); + } + if (mVels) { + MemoryPoolManager::DeleteBlock(mVels); + } + if (mSpeed) { + MemoryPoolManager::DeleteBlock(mSpeed); + } +} + +bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *boneMask) { + mPrevTime = currTime; + + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + float evalTime = currTime + mOffset; + float t0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; + float t1 = mFreq * mCycles[1] * evalTime + mAlignFrame[1]; + + mSkeleton->GetStillPose(sqtBuffer, 0); + if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { + return false; + } + + if (mWeight != 0.0f && mFnAnims[1]) { + ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); + unsigned int bufferSize = static_cast(mSkeleton->GetNumBones() * 12 * sizeof(float)); + + if (scratch.GetSize() < bufferSize) { + scratch.AllocateBuffer(bufferSize); + } + + float *blendPose = reinterpret_cast(scratch.GetBuffer()); + + mSkeleton->GetStillPose(blendPose, 0); + if (!mFnAnims[1]->EvalSQT(t1, blendPose, 0)) { + return false; + } + + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, boneMask); + } + + return true; +} + +void FnRunBlender::Eval(float prevTime, float currTime, float *pose) { + EvalSQT(currTime, pose, nullptr); +} + +void FnRunBlender::SetWeight(float w) { + int idx = static_cast(w); + + if (idx < 0) { + idx = 0; + } + if (idx >= mNumAnims - 1) { + idx = mNumAnims - 2; + } + + mWeight = w - static_cast(idx); + + if (idx == mIdx) { + float denom = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + + if (denom != 0.0f) { + float prevFreq = mFreq; + + mFreq = denom; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; + } + return; + } + + if (idx >= 0 && idx < mNumAnims) { + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx]))); + mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx + 1]))); + + if (mVels) { + mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx]))); + mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx + 1]))); + } + + mIdx = idx; + mCycles[0] = 1.0f; + mCycles[1] = 1.0f; + mFreq = 1.0f; + mOffset = 0.0f; + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h index 52745e831..ae018ae44 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h @@ -30,11 +30,20 @@ class FnRunBlender : public FnAnim { return ptr; } - inline float GetOffset() const {} + inline float GetOffset() const { + return mOffset; + } - inline void GetAnims(int &numAnims, const AnimMemoryMap **&anims, const PhaseChan **&phases, const AnimMemoryMap **&vels) const {} + inline void GetAnims(int &numAnims, const AnimMemoryMap **&anims, const PhaseChan **&phases, const AnimMemoryMap **&vels) const { + numAnims = mNumAnims; + anims = mAnims; + phases = mPhases; + vels = mVels; + } - inline float GetWeight() const {} + inline float GetWeight() const { + return mWeight; + } FnRunBlender(); diff --git a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h index cac4159d8..1d126f44c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h +++ b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h @@ -28,9 +28,13 @@ class ScratchBuffer { // void *operator new(size_t, void *ptr) {} - void *GetBuffer() {} + void *GetBuffer() { + return mBuffer; + } - unsigned int GetSize() const {} + unsigned int GetSize() const { + return mSize; + } void AllocateBuffer(unsigned int bufferSize); From 6fbf86887ce2fb08924a5a36f57d388d18311f44 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:16:16 +0100 Subject: [PATCH 045/372] 52.5%: fill FnRunBlender helper surface --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 146 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h | 2 +- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index c8e3bd997..99db31f83 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -2,6 +2,7 @@ #include "FnPoseBlender.h" #include "MemoryPoolManager.h" +#include "PhaseChan.h" #include "ScratchBuffer.h" @@ -144,4 +145,149 @@ void FnRunBlender::SetWeight(float w) { } } +bool FnRunBlender::EvalPhase(float currTime, PhaseValue &phase) { + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + if (mPhases && mIdx >= 0 && mIdx < mNumAnims) { + FnPhaseChan phaseChan; + + phaseChan.SetAnimMemoryMap(const_cast(mPhases[mIdx])); + phaseChan.Eval(0.0f, currTime, &phase.mAngle); + return true; + } + + return false; +} + +bool FnRunBlender::EvalVel2D(float currTime, float *vel) { + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + float evalTime = currTime + mOffset; + float t0 = CycleTime(mFreq * mCycles[0] * evalTime + mAlignFrame[0], 0.0f, mCycles[0]); + float t1 = CycleTime(mFreq * mCycles[1] * evalTime + mAlignFrame[1], 0.0f, mCycles[1]); + + if (mWeight != 0.0f && mFnVelAnims[0] && mFnVelAnims[1]) { + if (!BlendVel(t0, t1, vel)) { + return false; + } + } else if (mFnVelAnims[0]) { + if (!mFnVelAnims[0]->EvalVel2D(t0, vel)) { + return false; + } + } else { + return false; + } + + AlignVel(vel); + return true; +} + +bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { + float vel0[2]; + float vel1[2]; + + if (!mFnVelAnims[0] || !mFnVelAnims[1]) { + return false; + } + if (!mFnVelAnims[0]->EvalVel2D(t0, vel0) || !mFnVelAnims[1]->EvalVel2D(t1, vel1)) { + return false; + } + + vel[0] = vel0[0] + mWeight * (vel1[0] - vel0[0]); + vel[1] = vel0[1] + mWeight * (vel1[1] - vel0[1]); + return true; +} + +bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { + if (!f) { + return false; + } + + f[0] = 0.0f; + f[1] = 0.0f; + return false; +} + +float FnRunBlender::GetFrequency() const { + return mFreq; +} + +void FnRunBlender::ComputeBeginRootQ(UMath::Vector4 &q) const { + q.x = 0.0f; + q.y = 0.0f; + q.z = 0.0f; + q.w = 1.0f; +} + +void FnRunBlender::ComputeEndRootQ(UMath::Vector4 &q) const { + q.x = 0.0f; + q.y = 0.0f; + q.z = 0.0f; + q.w = 1.0f; +} + +void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { + q.x = 0.0f; + q.y = 0.0f; + q.z = 0.0f; + q.w = 1.0f; +} + +float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { + float length = endTime - startTime; + + if (length <= 0.0f) { + return startTime; + } + while (t < startTime) { + t += length; + } + while (t > endTime) { + t -= length; + } + return t; +} + +int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { + float length = endTime - startTime; + + if (length <= 0.0f) { + return 0; + } + + return static_cast((t - startTime) / length); +} + +void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { + q.x = 0.0f; + q.y = 0.0f; + q.z = 0.0f; + q.w = 1.0f; +} + +void FnRunBlender::AlignCycleBeginEnd(int cIdx) { + mCycleIdx = cIdx; + mInit = true; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; +} + +void FnRunBlender::AlignRootQ(float *sqt) const {} + +void FnRunBlender::AlignVel(float *vel) const {} + +bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { + if (!mPhases || mNumAnims <= 0) { + return false; + } + + return mPhases[0]->FindMatchTime(input, time); +} + }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h index ae018ae44..199b82da3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h @@ -61,7 +61,7 @@ class FnRunBlender : public FnAnim { void SetAnims(Skeleton *s, int numAnims, const AnimMemoryMap **anims, const AnimMemoryMap **phases, const AnimMemoryMap **vels); // Overrides: FnAnim - bool EvalPhase(float) override; + bool EvalPhase(float currTime, PhaseValue &phase) override; // Overrides: FnAnim bool EvalVel2D(float currTime, float *vel) override; From bdbaa6c2100577890c7773576b87189ca7e587cd Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:17:45 +0100 Subject: [PATCH 046/372] 53.4%: add FnTurnBlender first pass --- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 216 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h | 2 +- 2 files changed, 217 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index e69de29bb..1f198ee2f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -0,0 +1,216 @@ +#include "FnTurnBlender.h" + +#include "FnPoseBlender.h" +#include "ScratchBuffer.h" + + +namespace EAGL4Anim { + +FnTurnBlender::FnTurnBlender() + : mAnims(nullptr), // + mWeight(0.0f), // + mNumAnims(0), // + mIdx(-100), // + mSkeleton(nullptr), // + mFreq(1.0f), // + mPrevTime(0.0f), // + mOffset(0.0f), // + mCycleIdx(-100), // + mInit(false) { + mType = AnimTypeId::ANIM_TURNBLENDER; + mFnAnims[0] = nullptr; + mFnAnims[1] = nullptr; + mCycles[0] = 0.0f; + mCycles[1] = 0.0f; + mOffsets[0] = 0.0f; + mOffsets[1] = 0.0f; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; +} + +FnTurnBlender::~FnTurnBlender() {} + +bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *boneMask) { + mPrevTime = currTime; + + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + float evalTime = currTime + mOffset; + float t0 = CycleTime(mFreq * mCycles[0] * evalTime + mOffsets[0], 0.0f, mCycles[0]); + float t1 = CycleTime(mFreq * mCycles[1] * evalTime + mOffsets[1], 0.0f, mCycles[1]); + + mSkeleton->GetStillPose(sqtBuffer, 0); + if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { + return false; + } + + if (mWeight != 0.0f && mFnAnims[1]) { + ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); + unsigned int bufferSize = static_cast(mSkeleton->GetNumBones() * 12 * sizeof(float)); + + if (scratch.GetSize() < bufferSize) { + scratch.AllocateBuffer(bufferSize); + } + + float *blendPose = reinterpret_cast(scratch.GetBuffer()); + + mSkeleton->GetStillPose(blendPose, 0); + if (!mFnAnims[1]->EvalSQT(t1, blendPose, 0)) { + return false; + } + + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, boneMask); + } + + AlignRootQ(sqtBuffer); + return true; +} + +void FnTurnBlender::Eval(float prevTime, float currTime, float *pose) { + EvalSQT(currTime, pose, nullptr); +} + +void FnTurnBlender::SetWeight(float w) { + int idx = static_cast(w); + + if (idx < 0) { + idx = 0; + } + if (idx >= mNumAnims - 1) { + idx = mNumAnims - 2; + } + + mWeight = w - static_cast(idx); + + if (idx >= 0 && idx < mNumAnims) { + mFnAnims[0] = mAnims[idx]; + mFnAnims[1] = mAnims[idx + 1]; + mIdx = idx; + mCycles[0] = 1.0f; + mCycles[1] = 1.0f; + mOffsets[0] = 0.0f; + mOffsets[1] = 0.0f; + mFreq = 1.0f; + mOffset = 0.0f; + } +} + +bool FnTurnBlender::EvalPhase(float currTime, PhaseValue &phase) { + return false; +} + +bool FnTurnBlender::EvalVel2D(float currTime, float *vel) { + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + float evalTime = currTime + mOffset; + float t0 = CycleTime(mFreq * mCycles[0] * evalTime + mOffsets[0], 0.0f, mCycles[0]); + float t1 = CycleTime(mFreq * mCycles[1] * evalTime + mOffsets[1], 0.0f, mCycles[1]); + + if (mWeight != 0.0f && mFnAnims[0] && mFnAnims[1]) { + if (!BlendVel(t0, t1, vel)) { + return false; + } + } else if (mFnAnims[0]) { + if (!mFnAnims[0]->EvalVel2D(t0, vel)) { + return false; + } + } else { + return false; + } + + AlignVel(vel); + return true; +} + +bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { + float vel0[2]; + float vel1[2]; + + if (!mFnAnims[0] || !mFnAnims[1]) { + return false; + } + if (!mFnAnims[0]->EvalVel2D(t0, vel0) || !mFnAnims[1]->EvalVel2D(t1, vel1)) { + return false; + } + + vel[0] = vel0[0] + mWeight * (vel1[0] - vel0[0]); + vel[1] = vel0[1] + mWeight * (vel1[1] - vel0[1]); + return true; +} + +float FnTurnBlender::GetFrequency() const { + return mFreq; +} + +float FnTurnBlender::CycleTime(float t, float startTime, float endTime) const { + float length = endTime - startTime; + + if (length <= 0.0f) { + return startTime; + } + while (t < startTime) { + t += length; + } + while (t > endTime) { + t -= length; + } + return t; +} + +int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { + float length = endTime - startTime; + + if (length <= 0.0f) { + return 0; + } + + return static_cast((t - startTime) / length); +} + +void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { + q.x = 0.0f; + q.y = 0.0f; + q.z = 0.0f; + q.w = 1.0f; +} + +void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { + mCycleIdx = cIdx; + mInit = true; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; +} + +void FnTurnBlender::AlignRootQ(float *sqt) const {} + +void FnTurnBlender::AlignVel(float *vel) const {} + +bool FnTurnBlender::BlendBeginFacing(float *f) const { + if (!f) { + return false; + } + + f[0] = 0.0f; + f[1] = 0.0f; + return false; +} + +bool FnTurnBlender::BlendEndFacing(float *f) const { + if (!f) { + return false; + } + + f[0] = 0.0f; + f[1] = 0.0f; + return false; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h index 1a34dd8e9..0f23574f0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h @@ -45,7 +45,7 @@ class FnTurnBlender : public FnAnim { void SetAnims(Skeleton *s, int numAnims, FnAnim **anims); // Overrides: FnAnim - bool EvalPhase(float) override; + bool EvalPhase(float currTime, PhaseValue &phase) override; // Overrides: FnAnim bool EvalVel2D(float currTime, float *vel) override; From 3c0892a6efcbb07dd275d04562ef1ef0f400ff88 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:22:51 +0100 Subject: [PATCH 047/372] 53.5%: improve delta eval dispatch shape --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 7 ++-- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 6 +-- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 39 +++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index bbe944eb4..edeaa14d6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -136,12 +136,12 @@ void FnDeltaQ::Eval(float prevTime, float currTime, float *sqt) { } bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { - if (!mBins) { - InitBuffersAsRequired(); - } if (boneMask) { return EvalSQTMasked(currTime, boneMask, sqt); } + if (!mBins) { + InitBuffersAsRequired(); + } DeltaQ *deltaQ = reinterpret_cast(mpAnim); int floorTime = FloatToInt(currTime); @@ -499,7 +499,6 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq } } - mPrevKey = -1; return true; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index cc573edab..67a5e10d5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -186,12 +186,12 @@ void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { } bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { - if (!mPrevQs) { - InitBuffersAsRequired(); - } if (boneMask) { return EvalSQTMasked(currTime, boneMask, sqt); } + if (!mPrevQs) { + InitBuffersAsRequired(); + } DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); int floorTime = FloatToInt(currTime); diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index e69de29bb..a3c972655 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -0,0 +1,39 @@ +#include "Skeleton.h" + + +namespace EAGL4Anim { + +void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const BoneMask *mask) { + for (int ibone = 0; ibone < GetNumBones(); ibone++) { + if (!mask || mask->GetBone(ibone)) { + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + + if (mirrorBone < 0 || mirrorBone >= GetNumBones()) { + mirrorBone = ibone; + } + + float *dst = &mirrorPose[ibone * 12]; + const float *src = &pose[mirrorBone * 12]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = -src[5]; + dst[6] = -src[6]; + dst[7] = src[7]; + dst[8] = -src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + + if (!local) { + dst[4] = -dst[4]; + dst[7] = -dst[7]; + } + } + } +} + +}; // namespace EAGL4Anim From 127940875cf04d6eb0c6eeae69c11138d7d89ab9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:40:04 +0100 Subject: [PATCH 048/372] 56.2%: restore raw pose and state helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/SourceLists/zEagl4Anim.cpp | 2 + src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp | 41 ++++- .../Indep/Src/EAGL4Anim/FnEventBlender.h | 5 +- .../Indep/Src/EAGL4Anim/FnRawPoseChannel.h | 5 +- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 4 + src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp | 15 ++ .../Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 161 +++++++++++++++++ .../Indep/Src/EAGL4Anim/RawPoseChannel.h | 44 +++-- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 166 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h | 30 +++- .../Indep/Src/EAGL4Anim/ScratchBuffer.cpp | 16 ++ src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h | 1 + 12 files changed, 467 insertions(+), 23 deletions(-) diff --git a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp index b14224997..20f14b44a 100644 --- a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp +++ b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp @@ -34,6 +34,8 @@ #include "Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp" +#include "Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp" + #include "Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp" #include "Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp" diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp index af2ce0242..81015e8c8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp @@ -524,13 +524,32 @@ bool FnKeyQuatChan::EvalSQTMask(float currTime, float *sqt, const BoneMask *bone float qt0[7]; // TODO move -inline void QuatF4(float *&data, float *output) { +void QuatF4(float *&data, float *output) { output[0] = *data++; output[1] = *data++; output[2] = *data++; output[3] = *data++; } +// TODO inline and move +void EulF3(float *&data, float *output) { + const float degreesToRadians = 0.017453294f; + float x = *data++ * degreesToRadians * 0.5f; + float y = *data++ * degreesToRadians * 0.5f; + float z = *data++ * degreesToRadians * 0.5f; + float cx = cosf(x); + float cy = cosf(y); + float cz = cosf(z); + float sx = sinf(x); + float sy = sinf(y); + float sz = sinf(z); + + output[0] = cy * sx * cz - sy * cx * sz; + output[2] = cy * cx * sz - sy * sx * cz; + output[1] = cy * sx * sz + sy * cx * cz; + output[3] = cy * cx * cz + sy * sx * sz; +} + // TODO inline and move void TranF3(float *&data, float *output) { output[4] = *data++; @@ -545,4 +564,24 @@ void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { FastQuatBlendF4(w, qt0, output, output); } +// TODO inline and move +void EulF3Interp(float w, float *&data0, float *&data1, float *output) { + EulF3(data0, qt0); + EulF3(data1, output); + FastQuatBlendF4(w, qt0, output, output); +} + +// TODO inline and move +void TranF3Interp(float w, float *&data0, float *&data1, float *output) { + qt0[4] = *data0++; + qt0[5] = *data0++; + qt0[6] = *data0++; + output[4] = *data1++; + output[5] = *data1++; + output[6] = *data1++; + output[4] = qt0[4] + w * (output[4] - qt0[4]); + output[5] = qt0[5] + w * (output[5] - qt0[5]); + output[6] = qt0[6] + w * (output[6] - qt0[6]); +} + }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h index 374581e32..4526c5775 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h @@ -6,6 +6,7 @@ #endif #include "FnAnim.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -30,7 +31,9 @@ class FnEventBlender : public FnAnim { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h index feafed1fd..fe9194309 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h @@ -5,6 +5,7 @@ #pragma once #endif +#include "eagl4supportdef.h" #include "FnAnimMemoryMap.h" #include "RawPoseChannel.h" @@ -24,7 +25,9 @@ class FnRawPoseChannel : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 99db31f83..ff34df932 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -5,6 +5,10 @@ #include "PhaseChan.h" #include "ScratchBuffer.h" +float length(float *v) { + return sqrtf(v[0] * v[0] + v[1] * v[1]); +} + namespace EAGL4Anim { diff --git a/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp index e69de29bb..131f72172 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp @@ -0,0 +1,15 @@ +#include "PoseAnim.h" + +#include "FnPoseAnim.h" + +namespace EAGL4Anim { + +void PoseAnim::InitAnimMemoryMap(AnimMemoryMap *anim) { + PoseAnim *poseAnim = reinterpret_cast(anim); + FnPoseAnim fnPoseAnim; + FnPoseAnim *fnPoseAnimPtr = reinterpret_cast(poseAnim->GetFnLocation()); + + *reinterpret_cast(fnPoseAnimPtr) = *reinterpret_cast(&fnPoseAnim); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index e69de29bb..c06d4c4c1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -0,0 +1,161 @@ +#include "RawPoseChannel.h" + +namespace EAGL4Anim { + +void QuatF4(float *&data, float *output); +void EulF3(float *&data, float *output); +void TranF3(float *&data, float *output); +void QuatF4Interp(float w, float *&data0, float *&data1, float *output); +void EulF3Interp(float w, float *&data0, float *&data1, float *output); +void TranF3Interp(float w, float *&data0, float *&data1, float *output); + +void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { + RawPoseChannel *rawPoseChannel = reinterpret_cast(anim); + int numSigs = rawPoseChannel->mSigSize; + int *sig = rawPoseChannel->GetNonInterpSig(); + + for (int isig = 0; isig < numSigs; isig++) { + int numChannels = *sig++; + + for (int ichan = 0; ichan < numChannels; ichan++) { + switch (*sig) { + case QUAT: + *sig = reinterpret_cast(QuatF4); + break; + case EUL: + *sig = reinterpret_cast(EulF3); + break; + case TRAN: + *sig = reinterpret_cast(TranF3); + break; + } + sig++; + } + } + + sig = rawPoseChannel->GetInterpSig(); + for (int isig = 0; isig < numSigs; isig++) { + int numChannels = *sig++; + + for (int ichan = 0; ichan < numChannels; ichan++) { + switch (*sig) { + case QUAT: + *sig = reinterpret_cast(QuatF4Interp); + break; + case EUL: + *sig = reinterpret_cast(EulF3Interp); + break; + case TRAN: + *sig = reinterpret_cast(TranF3Interp); + break; + } + sig++; + } + } +} + +void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const BoneMask *boneMask) { + int frame = static_cast(frameTime); + + if (frame < 0) { + EvalFrame(0, outputPose, boneMask); + } else { + int lastFrame = mNumFrames - 1; + + if (frame < lastFrame) { + float t = frameTime - static_cast(frame); + + if (t == 0.0f || !interp) { + EvalFrame(frame, outputPose, boneMask); + } else { + int *sig = GetInterpSig(); + int *sigEnd = sig + mSigSize; + float *data0 = GetFrame(frame); + float *data1 = GetFrame(frame + 1); + + if (!boneMask) { + while (sig < sigEnd) { + int numChannels = *sig++; + float *bonePose = outputPose; + + outputPose = bonePose + 12; + for (int ichan = 0; ichan < numChannels; ichan++) { + reinterpret_cast(*sig++)(t, data0, data1, + bonePose + 4); + } + } + } else { + for (unsigned int ibone = 0; sig < sigEnd; ibone++) { + int numChannels = *sig++; + + if (boneMask->GetBone(ibone)) { + for (int ichan = 0; ichan < numChannels; ichan++) { + reinterpret_cast(*sig++)( + t, data0, data1, outputPose + 4); + } + } else { + for (int ichan = 0; ichan < numChannels; ichan++) { + void (*func)(float, float *&, float *&, float *) = + reinterpret_cast(*sig++); + + if (func == EulF3Interp || func == TranF3Interp) { + data0 += 3; + data1 += 3; + } else if (func == QuatF4Interp) { + data0 += 4; + data1 += 4; + } + } + } + + outputPose += 12; + } + } + } + } else { + EvalFrame(lastFrame, outputPose, boneMask); + } + } +} + +void RawPoseChannel::EvalFrame(int frame, float *outputPose, const BoneMask *boneMask) { + int *sig = GetNonInterpSig(); + int *sigEnd = sig + mSigSize; + float *frameData = GetFrame(frame); + + if (!boneMask) { + while (sig < sigEnd) { + int numChannels = *sig++; + float *bonePose = outputPose; + + outputPose = bonePose + 12; + for (int ichan = 0; ichan < numChannels; ichan++) { + reinterpret_cast(*sig++)(frameData, bonePose + 4); + } + } + } else { + for (unsigned int ibone = 0; sig < sigEnd; ibone++) { + int numChannels = *sig++; + + if (boneMask->GetBone(ibone)) { + for (int ichan = 0; ichan < numChannels; ichan++) { + reinterpret_cast(*sig++)(frameData, outputPose + 4); + } + } else { + for (int ichan = 0; ichan < numChannels; ichan++) { + void (*func)(float *&, float *) = reinterpret_cast(*sig++); + + if (func == EulF3 || func == TranF3) { + frameData += 3; + } else if (func == QuatF4) { + frameData += 4; + } + } + } + + outputPose += 12; + } + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h index 8c2adfa43..d2f4c596e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h @@ -19,33 +19,55 @@ class RawPoseChannel : public AnimMemoryMap { TRAN = 2, }; - int GetSigSize() const {} + int GetSigSize() const { + return mSigSize; + } void SetSigSize(int s) {} - int GetFrameSize() const {} + int GetFrameSize() const { + return mFrameSize; + } void SetFrameSize(int s) {} - int GetNumFrames() const {} + int GetNumFrames() const { + return mNumFrames; + } void SetNumFrames(int n) {} - int *GetNonInterpSig() {} + int *GetNonInterpSig() { + return reinterpret_cast(&this[1]); + } - const int *GetNonInterpSig() const {} + const int *GetNonInterpSig() const { + return reinterpret_cast(&this[1]); + } - int *GetInterpSig() {} + int *GetInterpSig() { + return GetNonInterpSig() + mSigSize; + } - const int *GetInterpSig() const {} + const int *GetInterpSig() const { + return GetNonInterpSig() + mSigSize; + } - float *GetAnimData() {} + float *GetAnimData() { + return reinterpret_cast(GetInterpSig() + mSigSize); + } - const float *GetAnimData() const {} + const float *GetAnimData() const { + return reinterpret_cast(GetInterpSig() + mSigSize); + } - float *GetFrame(int i) {} + float *GetFrame(int i) { + return &GetAnimData()[i * mFrameSize]; + } - const float *GetFrame(int i) const {} + const float *GetFrame(int i) const { + return &GetAnimData()[i * mFrameSize]; + } int GetSize() const {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index e69de29bb..9cab8cd43 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -0,0 +1,166 @@ +#include "RawStateChan.h" + +namespace EAGL4Anim { + +namespace { + +static unsigned char *GetRawStateKeyData(RawStateChan *rawStateChan) { + unsigned char *keyData = reinterpret_cast(rawStateChan->GetDecodeData()); + unsigned char numFields = rawStateChan->GetNumFields(); + + keyData += numFields * sizeof(unsigned short); + if ((numFields & 1) == 0) { + keyData += sizeof(unsigned short); + } + + return keyData; +} + +static const unsigned char *GetRawStateKeyData(const RawStateChan *rawStateChan) { + const unsigned char *keyData = reinterpret_cast(rawStateChan->GetDecodeData()); + unsigned char numFields = rawStateChan->GetNumFields(); + + keyData += numFields * sizeof(unsigned short); + if ((numFields & 1) == 0) { + keyData += sizeof(unsigned short); + } + + return keyData; +} + +} // namespace + +FnRawStateChan::~FnRawStateChan() {} + +bool FnRawStateChan::GetLength(float &timeLength) const { + const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + + timeLength = static_cast(rawStateChan->GetNumFrames()); + return true; +} + +void FnRawStateChan::Eval(float, float time, float *dofs) { + EvalState(time, reinterpret_cast(dofs)); +} + +void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { + const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + unsigned int value = 0; + unsigned int numBits = 0; + + for (int i = 0; i < rawStateChan->GetNumFields(); i++) { + unsigned short decodeData = rawStateChan->GetDecodeData()[i]; + unsigned short storedNumBitsInPowersOf2 = decodeData >> 13; + unsigned char destNumBytes = static_cast(((decodeData >> 11) & 3) + 1); + unsigned char destByteOffset = static_cast(decodeData); + + switch (storedNumBitsInPowersOf2) { + case 0: + numBits = (numBits + 1) & 0xFF; + value = (*src >> ((8 - numBits) & 0x1F)) & 1; + break; + case 1: + numBits = (numBits + 2) & 0xFF; + value = (*src >> ((8 - numBits) & 0x1F)) & 3; + break; + case 2: + numBits = (numBits + 4) & 0xFF; + value = (*src >> ((8 - numBits) & 0x3F)) & 0xF; + break; + case 3: + value = *src; + src += 1; + break; + case 4: + value = *reinterpret_cast(src); + src += 2; + break; + case 5: + value = *reinterpret_cast(src); + src += 4; + break; + } + + if (numBits > 7) { + src += 1; + numBits = 0; + } + + switch (destNumBytes) { + case 1: + *reinterpret_cast(&dest[destByteOffset]) = static_cast(value); + break; + case 2: + *reinterpret_cast(&dest[destByteOffset]) = static_cast(value); + break; + case 4: + *reinterpret_cast(&dest[destByteOffset]) = value; + break; + } + } +} + +bool FnRawStateChan::EvalState(float time, State *s) { + RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + unsigned char keySize = rawStateChan->GetKeySize(); + unsigned char *keyData = GetRawStateKeyData(rawStateChan); + int keyIdx = mKeyIdx; + + if (*reinterpret_cast(&keyData[keyIdx * keySize]) > time) { + keyIdx--; + + while (keyIdx >= 0) { + float *keyTime = reinterpret_cast(&keyData[keyIdx * keySize]); + + if (*keyTime <= time) { + Decode(reinterpret_cast(keyTime) + sizeof(float), reinterpret_cast(s)); + mKeyIdx = keyIdx; + return true; + } + + keyIdx--; + } + + Decode(keyData + sizeof(float), reinterpret_cast(s)); + mKeyIdx = 0; + } else { + for (; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { + unsigned char *currKey = &keyData[keyIdx * keySize]; + + if (time < *reinterpret_cast(currKey + keySize)) { + Decode(currKey + sizeof(float), reinterpret_cast(s)); + mKeyIdx = keyIdx; + return true; + } + } + + Decode(keyData + keySize * (rawStateChan->GetNumKeys() - 1) + sizeof(float), reinterpret_cast(s)); + mKeyIdx = rawStateChan->GetNumKeys() - 1; + } + + return true; +} + +bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &resultTime) { + const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + const unsigned char *keyData = GetRawStateKeyData(rawStateChan); + unsigned char keySize = rawStateChan->GetKeySize(); + unsigned char stateBuffer[84]; + + for (int keyIdx = 0; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { + const unsigned char *currKey = &keyData[keyIdx * keySize]; + float keyTime = *reinterpret_cast(currKey); + + if (startTime < keyTime) { + Decode(const_cast(currKey + sizeof(float)), stateBuffer); + if (test.Pass(reinterpret_cast(stateBuffer))) { + resultTime = keyTime; + return true; + } + } + } + + return false; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h index afce8cf96..57be55aac 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h @@ -29,23 +29,35 @@ class RawStateChan : public AnimMemoryMap { void SetNumFrames(unsigned short n) {} - unsigned short GetNumFrames() const {} + unsigned short GetNumFrames() const { + return mNumFrames; + } void SetNumKeys(unsigned short n) {} - unsigned short GetNumKeys() const {} + unsigned short GetNumKeys() const { + return mNumKeys; + } void SetKeySize(unsigned char n) {} - unsigned char GetKeySize() const {} + unsigned char GetKeySize() const { + return mKeySize; + } void SetNumFields(unsigned char n) {} - unsigned char GetNumFields() const {} + unsigned char GetNumFields() const { + return mNumFields; + } - const unsigned short *GetDecodeData() const {} + const unsigned short *GetDecodeData() const { + return mDecodeData; + } - unsigned short *GetDecodeData() {} + unsigned short *GetDecodeData() { + return mDecodeData; + } int GetSize() const {} @@ -91,13 +103,13 @@ class FnRawStateChan : public FnAnimMemoryMap { } // Overrides: FnAnimSuper - ~FnRawStateChan() override {} + ~FnRawStateChan() override; // Overrides: FnAnim - bool GetLength(float &timeLength) const override {} + bool GetLength(float &timeLength) const override; // Overrides: FnAnim - void Eval(float, float time, float *dofs) override {} + void Eval(float, float time, float *dofs) override; void Decode(unsigned char *src, unsigned char *dest) const; diff --git a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp index 7b31e139f..ae481ffa9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp @@ -1,7 +1,23 @@ #include "ScratchBuffer.h" +#include "Speed/Indep/Src/EAGL4Anim/eagl4supportdef.h" + namespace EAGL4Anim { ScratchBuffer ScratchBufferHelper::mScratchBuffers[] = {}; +void ScratchBuffer::FreeBuffer() { + int refCount = mRefCount; + + mRefCount = refCount - 1; + if (mBuffer && refCount - 1 < 1) { + EAGL4Internal::EAGL4Free(mBuffer, mSize); + mBuffer = nullptr; + } +} + +ScratchBuffer &ScratchBuffer::GetScratchBuffer(int i) { + return ScratchBufferHelper::mScratchBuffers[i]; +} + }; diff --git a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h index 1d126f44c..fedeb56a2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h +++ b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h @@ -52,6 +52,7 @@ class ScratchBuffer { class ScratchBufferHelper { private: + friend class ScratchBuffer; static ScratchBuffer mScratchBuffers[3]; // size: 0x24 }; From 66aaa2b83d6e76abafc5830705dd94bd6fafc59a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:47:44 +0100 Subject: [PATCH 049/372] 57.4%: match skeleton and event helpers --- src/Speed/Indep/SourceLists/zEagl4Anim.cpp | 2 + src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h | 20 +-- src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h | 21 +-- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 18 +++ .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 18 +++ .../Indep/Src/EAGL4Anim/RawEventChannel.cpp | 112 +++++++++++++++ .../Indep/Src/EAGL4Anim/RawEventChannel.h | 20 ++- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 132 ++++++++++++++++++ src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp | 36 +++++ src/Speed/Indep/Src/EAGL4Anim/system.cpp | 13 ++ 10 files changed, 350 insertions(+), 42 deletions(-) diff --git a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp index 20f14b44a..dc95d275e 100644 --- a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp +++ b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp @@ -70,4 +70,6 @@ #include "Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp" +#include "Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp" + #include "Speed/Indep/Src/EAGL4Anim/system.cpp" diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h index 718216a3f..caca67ba4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h @@ -108,25 +108,9 @@ struct DeltaQ : public AnimMemoryMap { DeltaQDelta *GetDelta(unsigned char *binData, int deltaIdx) {} - unsigned short *GetConstBoneIdx() { - unsigned int binLen = GetBinLength(); - const int binSize = GetBinSize(); - // TODO it's out of line - // int numBins = mNumFrames >> GetBinLengthPower(); // r8 - // // get to the end of the bins - // unsigned char *s = &GetBin(0)[binSize * numBins]; // r11 - // int r = mNumFrames & GetBinLengthModMask(); // r31 - - // if (r > 0) { - // s = reinterpret_cast(AlignSize2((intptr_t)s + mNumBones * 2 + (r - 1) * GetFrameDeltaSize())); - // } - - return reinterpret_cast(nullptr); - } + unsigned short *GetConstBoneIdx(); - float *GetConstPhysical() { - // return reinterpret_cast(AlignSize4(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); - } + float *GetConstPhysical(); unsigned short mNumKeys; // offset 0x4, size 0x2 unsigned char mNumBones; // offset 0x6, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h index ac72a1499..e743e6ce4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h @@ -108,26 +108,9 @@ struct DeltaQFast : public AnimMemoryMap { return reinterpret_cast(&binData[mNumBones * 6 + deltaIdx * mNumBones * 3]); } - unsigned short *GetConstBoneIdx() { - const int binSize = GetBinSize(); - int numFrames = GetNumFrames(); - int numBins = numFrames >> GetBinLengthPower(); - unsigned char *s = &GetBin(0)[binSize * numBins]; - int r = numFrames & GetBinLengthModMask(); - - if (r > 0) { - s = reinterpret_cast(AlignSize2(reinterpret_cast(s + mNumBones * 6 + ((r - 1) * mNumBones * 3)))); - } - if (mNumBones == 0) { - s = reinterpret_cast(AlignSize2(reinterpret_cast(s))); - } + unsigned short *GetConstBoneIdx(); - return reinterpret_cast(s); - } - - void *GetConstPhysical() { - return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); - } + void *GetConstPhysical(); unsigned short mNumKeys; // offset 0x4, size 0x2 unsigned char mNumBones; // offset 0x6, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index edeaa14d6..cc9b0a889 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -6,6 +6,24 @@ namespace EAGL4Anim { +unsigned short *DeltaQ::GetConstBoneIdx() { + const int binSize = GetBinSize(); + int numBins = mNumKeys >> GetBinLengthPower(); + unsigned char *s = &GetBin(0)[binSize * numBins]; + int r = mNumKeys & GetBinLengthModMask(); + + if (r > 0) { + s = reinterpret_cast( + AlignSize2(reinterpret_cast(s + mNumBones * sizeof(DeltaQPhysical) + ((r - 1) * mNumBones * sizeof(DeltaQDelta))))); + } + + return reinterpret_cast(s); +} + +float *DeltaQ::GetConstPhysical() { + return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); +} + namespace { static const float kFloatZero = 0.0f; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index e64885808..c685b9953 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -32,6 +32,24 @@ void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector namespace EAGL4Anim { +unsigned short *DeltaQFast::GetConstBoneIdx() { + const int binSize = GetBinSize(); + int numFrames = GetNumFrames(); + int numBins = numFrames >> GetBinLengthPower(); + unsigned char *s = &GetBin(0)[binSize * numBins]; + int r = numFrames & GetBinLengthModMask(); + + if (r > 0) { + s = reinterpret_cast(AlignSize2(reinterpret_cast(s + mNumBones * 6 + ((r - 1) * mNumBones * 3)))); + } + + return reinterpret_cast(s); +} + +void *DeltaQFast::GetConstPhysical() { + return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); +} + namespace { static const float kQFastMinScale16 = 3.0518044e-5f; diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp index e69de29bb..eeaccfe95 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp @@ -0,0 +1,112 @@ +#include "RawEventChannel.h" + +namespace EAGL4Anim { + +void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tIdx, float &cacheCurrentTime, EventHandler **eventHandlers, void *extraData) { + int numEvents = GetNumEvents(); + Event *events = GetEvents(); + int i; + + if (previousTime < cacheCurrentTime) { + i = currentIdx; + if (i > -1 && previousTime < events[i].triggerTime) { + float *eventTime = &events[i].triggerTime; + + do { + i--; + eventTime -= 4; + if (i < 0) { + break; + } + } while (previousTime < *eventTime); + } + currentIdx = i + 1; + } + + i = currentIdx; + bool validIdx = i < numEvents; + + if (validIdx && events[i].triggerTime <= previousTime) { + float *eventTime = &events[i].triggerTime; + + do { + i++; + validIdx = i < numEvents; + eventTime += 4; + if (!validIdx) { + break; + } + } while (*eventTime <= previousTime); + } + + currentIdx = i; + + if (previousTime == currentTime) { + if (!validIdx) { + currentIdx = numEvents - 1; + i = currentIdx; + } + + if (i > -1 && currentTime <= events[i].triggerTime) { + float *eventTime = &events[i].triggerTime; + + do { + i--; + eventTime -= 4; + if (i < 0) { + break; + } + } while (currentTime <= *eventTime); + } + + currentIdx = i + 1; + } else { + if (previousTime > currentTime) { + if (validIdx) { + Event *event = &events[i]; + i = numEvents - i; + + do { + EventHandler *eventHandler = eventHandlers[event->eventId]; + if (eventHandler) { + eventHandler->HandleEvent(currentTime, *event, extraData); + } + i--; + event++; + } while (i != 0); + } + + currentIdx = 0; + } + + i = currentIdx; + validIdx = i < numEvents; + if (validIdx) { + Event *event = &events[i]; + + do { + if (currentTime < event->triggerTime) { + break; + } + + EventHandler *eventHandler = eventHandlers[event->eventId]; + if (eventHandler) { + eventHandler->HandleEvent(currentTime, *event, extraData); + } + + i++; + validIdx = i < numEvents; + event++; + } while (validIdx); + } + + currentIdx = i; + if (!validIdx) { + currentIdx = numEvents - 1; + } + } + + cacheCurrentTime = currentTime; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h index e38fae196..9f639a4e8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h @@ -13,15 +13,25 @@ namespace EAGL4Anim { // total size: 0x8 class RawEventChannel : public AnimMemoryMap { public: - int GetNumEvents() const {} + int GetNumEvents() const { + return mNumEvents; + } - void SetNumEvents(int n) {} + void SetNumEvents(int n) { + mNumEvents = n; + } - struct Event *GetEvents() {} + Event *GetEvents() { + return reinterpret_cast(&this[1]); + } - int GetSize() const {} + int GetSize() const { + return ComputeSize(mNumEvents); + } - static int ComputeSize(int numEvents) {} + static int ComputeSize(int numEvents) { + return sizeof(RawEventChannel) + numEvents * sizeof(Event); + } void Eval(float previousTime, float currentTime, int ¤tIdx, float &cacheCurrentTime, EventHandler **eventHandlers, void *extraData); diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index a3c972655..ad756af0e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -1,8 +1,140 @@ #include "Skeleton.h" +static void MtxMult(EAGL4::Transform *result, const EAGL4::Transform *A, const EAGL4::Transform *B); namespace EAGL4Anim { +void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask *mask) { + int numBones = GetNumBones(); + + if (!mask) { + for (int i = 0; i < numBones; i++) { + float *bonePose = pose; + float *mat = output[i].m.GetElements(); + + output[i].BuildSQT(bonePose[0], bonePose[1], bonePose[2], bonePose[4], bonePose[5], bonePose[6], bonePose[7], bonePose[8], + bonePose[9], bonePose[10]); + mat[0] *= bonePose[3]; + mat[4] *= bonePose[3]; + mat[8] *= bonePose[3]; + + int parentIdx = GetBoneData(i).mParentIdx; + if (parentIdx > -1) { + MtxMult(&output[i], &output[parentIdx], &output[i]); + } + + pose += 12; + } + } else { + for (int i = 0; i < numBones; i++) { + if (mask->GetBone(i)) { + float *bonePose = pose; + float *mat = output[i].m.GetElements(); + + output[i].BuildSQT(bonePose[0], bonePose[1], bonePose[2], bonePose[4], bonePose[5], bonePose[6], bonePose[7], bonePose[8], + bonePose[9], bonePose[10]); + mat[0] *= bonePose[3]; + mat[4] *= bonePose[3]; + mat[8] *= bonePose[3]; + + int parentIdx = GetBoneData(i).mParentIdx; + if (parentIdx > -1) { + MtxMult(&output[i], &output[parentIdx], &output[i]); + } + } + + pose += 12; + } + } +} + +void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { + int numBones = GetNumBones(); + const float *invBoneScales = GetInvBoneScales(); + + if (!mask) { + if (!invBoneScales) { + for (int i = 0; i < numBones; i++) { + const BoneData &bone = GetBoneData(i); + + pose[0] = bone.mS.x; + pose[1] = bone.mS.y; + pose[2] = bone.mS.z; + pose[3] = 1.0f; + pose[4] = bone.mQ.x; + pose[5] = bone.mQ.y; + pose[6] = bone.mQ.z; + pose[7] = bone.mQ.w; + pose[8] = bone.mT.x; + pose[9] = bone.mT.y; + pose[10] = bone.mT.z; + pose[11] = 1.0f; + pose += 12; + } + } else { + for (int i = 0; i < numBones; i++) { + const BoneData &bone = GetBoneData(i); + + pose[0] = bone.mS.x; + pose[1] = bone.mS.y; + pose[2] = bone.mS.z; + pose[3] = invBoneScales[i]; + pose[4] = bone.mQ.x; + pose[5] = bone.mQ.y; + pose[6] = bone.mQ.z; + pose[7] = bone.mQ.w; + pose[8] = bone.mT.x; + pose[9] = bone.mT.y; + pose[10] = bone.mT.z; + pose[11] = 1.0f; + pose += 12; + } + } + } else { + if (!invBoneScales) { + for (unsigned int i = 0; i < static_cast(numBones); i++) { + if (mask->GetBone(i)) { + const BoneData &bone = GetBoneData(static_cast(i)); + + pose[0] = bone.mS.x; + pose[1] = bone.mS.y; + pose[2] = bone.mS.z; + pose[3] = 1.0f; + pose[4] = bone.mQ.x; + pose[5] = bone.mQ.y; + pose[6] = bone.mQ.z; + pose[7] = bone.mQ.w; + pose[8] = bone.mT.x; + pose[9] = bone.mT.y; + pose[10] = bone.mT.z; + pose[11] = 1.0f; + } + pose += 12; + } + } else { + for (unsigned int i = 0; i < static_cast(numBones); i++) { + if (mask->GetBone(i)) { + const BoneData &bone = GetBoneData(static_cast(i)); + + pose[0] = bone.mS.x; + pose[1] = bone.mS.y; + pose[2] = bone.mS.z; + pose[3] = invBoneScales[i]; + pose[4] = bone.mQ.x; + pose[5] = bone.mQ.y; + pose[6] = bone.mQ.z; + pose[7] = bone.mQ.w; + pose[8] = bone.mT.x; + pose[9] = bone.mT.y; + pose[10] = bone.mT.z; + pose[11] = 1.0f; + } + pose += 12; + } + } + } +} + void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const BoneMask *mask) { for (int ibone = 0; ibone < GetNumBones(); ibone++) { if (!mask || mask->GetBone(ibone)) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp b/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp index e69de29bb..725b621f6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp @@ -0,0 +1,36 @@ +#include "System.h" + +#include "FnAnimFactory.h" +#include "MemoryPoolManager.h" +#include "eagl4AnimBank.h" +#include "eagl4supportdlopen.h" + +#include + +namespace EAGL4Anim { + +namespace { + +struct MemoryPoolManagerAccessor : MemoryPoolManager { + static MemoryPoolManager *&GetDefaultMemoryManager() { + return gDefaultMemoryManager; + } + + static MemoryPoolManager *&GetMemoryManager() { + return gMemoryManager; + } +}; + +} // namespace + +void Initializer::InitInternal(size_t memorySize, bool) { + MemoryPoolManagerAccessor::GetDefaultMemoryManager() = + new (EAGL4Internal::EAGL4Malloc(sizeof(MemoryPoolManager), nullptr)) MemoryPoolManager; + MemoryPoolManagerAccessor::GetMemoryManager() = MemoryPoolManagerAccessor::GetDefaultMemoryManager(); + MemoryPoolManagerAccessor::GetDefaultMemoryManager()->InitAux(static_cast(memorySize)); + + FnAnimFactory::mpFactory = reinterpret_cast(EAGL4Internal::EAGL4Malloc(1, nullptr)); + EAGL4::DynamicLoader::gConsPool.AddType(EAGL4::DynamicLoader::AnimBankType, AnimBank::Constructor, AnimBank::Destructor); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/system.cpp b/src/Speed/Indep/Src/EAGL4Anim/system.cpp index e69de29bb..f5445b47d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/system.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/system.cpp @@ -0,0 +1,13 @@ +#include "eagl4runtimetransform.h" + +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "dolphin/mtx44_ext.h" + +static void MtxMult(EAGL4::Transform *result, const EAGL4::Transform *A, const EAGL4::Transform *B) { + EAGL4::Transform temp; + const Mtx44 &src = *reinterpret_cast(&temp); + Mtx44 &dst = *reinterpret_cast(result); + + bMulMatrix(reinterpret_cast(&temp), reinterpret_cast(A), reinterpret_cast(B)); + PSMTX44Copy(src, dst); +} From 7d1bd919a7b4d3a9c85fd51abd44c0bf9fb18850 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 21:53:16 +0100 Subject: [PATCH 050/372] 59.0%: recover dynamic loader setup --- .../Src/EAGL4Anim/eagl4supportconspool.h | 2 +- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 334 +++++++++++++++++- 2 files changed, 332 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h index f2fa079a2..6376dfb62 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h @@ -22,7 +22,7 @@ struct DestructorEntry { void *data; // offset 0x4, size 0x4 }; -typedef void *(*RuntimeAllocConstructor)(const char *, class DynamicLoader *, int &, bool &, const char *); +typedef void *(*RuntimeAllocConstructor)(const char *, class EAGL4::DynamicLoader *, int &, bool &, const char *); typedef void (*RuntimeAllocDestructor)(void *, int); // TODO wrong namespace diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 28f90eacf..6b7c04d12 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -5,8 +5,52 @@ #include #include +static void *dlsym(void *handle, const char *name); + namespace EAGL4 { +namespace { + +static const char kSymTabName[] = ".symtab"; +static const char kStrTabName[] = ".strtab"; +static const char kNamespaceMarker[] = ":::"; +static const char kDynamicSymbolsMsg[] = "EAGL4::dynamic symbols"; +static const char kDynamicSymbols2Msg[] = "EAGL4::dynamic symbols 2"; +static const char gRuntimeAllocType[] = "RUNTIME_ALLOC::"; + +static inline unsigned short ByteSwap16(unsigned short value) { + return static_cast((value << 8) | (value >> 8)); +} + +static inline unsigned int ByteSwap32(unsigned int value) { + return ((value >> 16) & 0xFF) << 8 | (value >> 24) | (((value & 0xFF) << 8 | ((value & 0xFFFF) >> 8)) << 16); +} + +static inline char *ResolveLoaderAddress(char *data, unsigned int dataLen, char *reloc, unsigned int offset) { + if (offset < dataLen) { + return data + offset; + } + if (reloc) { + return reloc + (offset - dataLen); + } + if (dataLen < offset) { + return nullptr; + } + return data + offset; +} + +static inline const char *GetLoaderSymbolType(const char *name) { + const char *type = name + strlen(name); + if (type[1] == 0x7F) { + type += 2; + } + return type + 1; +} + +} // namespace + +static HashPointer *hashhead; + SymbolPool DynamicLoader::gSymbolPool; ConstructorPool DynamicLoader::gConsPool; RuntimeAllocConstructorPool DynamicLoader::gRuntimeAllocConsPool; @@ -62,6 +106,293 @@ bool DynamicLoader::DoVersionCheck() { return true; } +void DynamicLoader::Resolve() { + HashPointer *h = reinterpret_cast(handle); + if (!h || h->resolved) { + return; + } + + h->resolved = true; + + ELFSectionHeader *sections = h->sections; + int unresolvedCount = 0; + int unresolvedSeen[32]; + int sectionCount = h->e->e_shnum; + + for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { + ELFSectionHeader *section = §ions[sectionIndex]; + if (section->sh_type != SHT_REL) { + continue; + } + + ELF32_Rel *rel = reinterpret_cast(section->sh_voffset); + int relCount = static_cast(section->sh_size); + if (relCount < 0) { + relCount += 7; + } + relCount >>= 3; + + ELF32_Sym *symtab = reinterpret_cast(section->sh_vlink); + char *targetBase = reinterpret_cast(section->sh_vinfo); + + for (int relIndex = 0; relIndex < relCount; relIndex++) { + rel[relIndex].r_offset = ByteSwap32(rel[relIndex].r_offset); + rel[relIndex].r_info = ByteSwap32(rel[relIndex].r_info); + + ELF32_Sym *sym = &symtab[rel[relIndex].r_info >> 8]; + int symbolBase = 0; + + while ((sym->st_info & 0xF) < STT_FILE) { + if (sym->st_shndx && sym->st_shndx < h->e->e_shnum) { + symbolBase = reinterpret_cast(sections[sym->st_shndx].sh_voffset) - reinterpret_cast(nullptr); + break; + } + + const char *name = &h->strtab[sym->st_name]; + bool valid = false; + void *resolvedAddr = nullptr; + + for (HashPointer *other = hashhead; other; other = other->next) { + if (other != h) { + resolvedAddr = dlsym(other, name); + if (resolvedAddr) { + sym->st_shndx = 1; + sym->st_other = 2; + sym->st_value = reinterpret_cast(resolvedAddr) - reinterpret_cast(sections[1].sh_voffset); + symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + break; + } + } + } + + if (resolvedAddr) { + break; + } + + if (h->pSearchFunction) { + resolvedAddr = h->pSearchFunction(name, valid); + if (valid) { + sym->st_shndx = 1; + sym->st_other = 3; + sym->st_value = reinterpret_cast(resolvedAddr) - reinterpret_cast(sections[1].sh_voffset); + symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + break; + } + } + + resolvedAddr = gSymbolPool.Search(name, valid); + if (valid) { + sym->st_shndx = 1; + sym->st_other = 4; + sym->st_value = reinterpret_cast(resolvedAddr) - reinterpret_cast(sections[1].sh_voffset); + symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + break; + } + + const char *type = GetLoaderSymbolType(name); + if (strncmp(gRuntimeAllocType, name, strlen(gRuntimeAllocType)) == 0) { + RuntimeAllocConstructor ctor = gRuntimeAllocConsPool.FindConstructor(type); + if (ctor) { + RuntimeAllocDestructor dtor = gRuntimeAllocConsPool.FindDestructor(type); + int auxData = 0; + bool createDestructor = false; + void *runtimeAlloc = + ctor(name + strlen(gRuntimeAllocType), reinterpret_cast(this), auxData, createDestructor, name); + + if (createDestructor && runtimeAlloc) { + RuntimeAllocDestructorEntry *entry = + reinterpret_cast(EAGL4Internal::EAGL4Malloc(sizeof(RuntimeAllocDestructorEntry), nullptr)); + entry->d = dtor; + entry->data = runtimeAlloc; + entry->auxData = auxData; + entry->next = RuntimeAllocDestructors; + RuntimeAllocDestructors = entry; + } + + sym->st_shndx = 1; + sym->st_other = 5; + sym->st_value = reinterpret_cast(runtimeAlloc) - reinterpret_cast(sections[1].sh_voffset); + symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + break; + } + } + + bool alreadySeen = false; + if (unresolvedCount <= 0x1F) { + for (int i = 0; i < unresolvedCount; i++) { + if (name == reinterpret_cast(unresolvedSeen[i])) { + alreadySeen = true; + break; + } + } + if (!alreadySeen) { + unresolvedSeen[unresolvedCount++] = reinterpret_cast(name); + } + } + break; + } + + int value = symbolBase + sym->st_value; + unsigned int *relTarget = reinterpret_cast(targetBase + rel[relIndex].r_offset); + unsigned int relValue = ByteSwap32(*relTarget); + + switch (rel[relIndex].r_info & 0xFF) { + case R_MIPS_32: + relValue += value; + break; + + case R_MIPS_26: + relValue = (relValue & 0xFC000000) | ((value + ((relValue & 0x03FFFFFF) * 4) & 0x0FFFFFFF) >> 2); + break; + + case R_MIPS_HI16: + *reinterpret_cast(relTarget) = + static_cast(*reinterpret_cast(relTarget) + static_cast(static_cast(value) >> 16)); + continue; + + case R_MIPS_LO16: + *reinterpret_cast(relTarget) = static_cast(*reinterpret_cast(relTarget) + static_cast(value)); + continue; + } + + *relTarget = ByteSwap32(relValue); + } + } + + RunConstructors(); + mIsResolved = true; +} + +void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { + HashPointer *h = reinterpret_cast(EAGL4Internal::EAGL4Malloc(sizeof(HashPointer), nullptr)); + + h->next = nullptr; + h->prev = nullptr; + h->strtab = nullptr; + h->resolved = false; + h->symbols_num = 0; + h->symtab = nullptr; + h->sections = nullptr; + h->e = reinterpret_cast(mpData); + h->chain = nullptr; + h->isOriginal = nullptr; + h->mpDynamicLoader = this; + h->pSearchFunction = pSearchFunction; + + ELFHeader *e = h->e; + + e->e_type = ByteSwap16(e->e_type); + e->e_machine = ByteSwap16(e->e_machine); + e->e_version = ByteSwap32(e->e_version); + e->e_entry = ByteSwap32(e->e_entry); + e->e_phoff = ByteSwap32(e->e_phoff); + e->e_shoff = ByteSwap32(e->e_shoff); + e->e_flags = ByteSwap32(e->e_flags); + e->e_ehsize = ByteSwap16(e->e_ehsize); + e->e_phentsize = ByteSwap16(e->e_phentsize); + e->e_phnum = ByteSwap16(e->e_phnum); + e->e_shentsize = ByteSwap16(e->e_shentsize); + e->e_shnum = ByteSwap16(e->e_shnum); + e->e_shstrndx = ByteSwap16(e->e_shstrndx); + + h->sections = reinterpret_cast(ResolveLoaderAddress(mpData, mDataLen, mpReloc, e->e_shoff)); + + for (int i = 0; i < e->e_shnum; i++) { + ELFSectionHeader *section = &h->sections[i]; + + section->sh_name = ByteSwap32(section->sh_name); + section->sh_type = ByteSwap32(section->sh_type); + section->sh_flags = ByteSwap32(section->sh_flags); + section->sh_addr = ByteSwap32(section->sh_addr); + section->sh_offset = ByteSwap32(section->sh_offset); + section->sh_size = ByteSwap32(section->sh_size); + section->sh_link = ByteSwap32(section->sh_link); + section->sh_info = ByteSwap32(section->sh_info); + section->sh_addralign = ByteSwap32(section->sh_addralign); + section->sh_entsize = ByteSwap32(section->sh_entsize); + + section->sh_voffset = ResolveLoaderAddress(mpData, mDataLen, mpReloc, section->sh_offset); + } + + char *sectionNameTable = reinterpret_cast(h->sections[e->e_shstrndx].sh_voffset); + + for (int i = 0; i < e->e_shnum; i++) { + ELFSectionHeader *section = &h->sections[i]; + + if (section->sh_type == SHT_STRTAB) { + section->sh_vlink = h->sections[section->sh_link].sh_voffset; + if (strcmp(kStrTabName, sectionNameTable + section->sh_name) == 0) { + char *stringEntry = reinterpret_cast(section->sh_voffset); + int remaining = section->sh_size; + + h->strtab = stringEntry; + while (remaining > 0) { + int len = strlen(stringEntry); + if (stringEntry[0] == '_' && stringEntry[1] == '_') { + char *scope = strstr(stringEntry + 2, kNamespaceMarker); + if (scope) { + char temp[128]; + *scope = 0; + strcpy(temp, stringEntry + 2); + int markerLen = strlen(kNamespaceMarker); + int tailLen = strlen(scope + markerLen); + memmove(stringEntry, scope + markerLen, tailLen + 1); + stringEntry[tailLen + 1] = '\x7F'; + strcpy(stringEntry + tailLen + 2, temp); + len = strlen(stringEntry); + } + } + stringEntry += len + 1; + remaining -= len + 1; + } + } + } else if (section->sh_type == SHT_SYMTAB || section->sh_type == SHT_STRTAB) { + section->sh_vlink = h->sections[section->sh_link].sh_voffset; + if (strcmp(kSymTabName, sectionNameTable + section->sh_name) == 0) { + h->symtab = reinterpret_cast(section->sh_voffset); + h->symbols_num = static_cast(section->sh_size >> 4); + } + } else if (section->sh_type == SHT_REL) { + section->sh_vinfo = h->sections[section->sh_info].sh_voffset; + section->sh_vlink = h->sections[section->sh_link].sh_voffset; + } + } + + for (int i = 0; i < 0x100; i++) { + h->hash[i] = static_cast(-1); + } + + h->chain = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h->symbols_num * sizeof(unsigned long), kDynamicSymbolsMsg)); + h->isOriginal = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h->symbols_num * sizeof(unsigned long), kDynamicSymbols2Msg)); + + for (int i = 0; i < h->symbols_num; i++) { + ELF32_Sym *sym = &h->symtab[i]; + + sym->st_name = ByteSwap32(sym->st_name); + sym->st_value = ByteSwap32(sym->st_value); + sym->st_size = ByteSwap32(sym->st_size); + sym->st_shndx = ByteSwap16(sym->st_shndx); + + unsigned long hash = elfhash(h->strtab + sym->st_name); + h->chain[i] = h->hash[hash]; + h->hash[hash] = i; + + if (sym->st_shndx == 0 || h->e->e_shnum <= sym->st_shndx) { + h->isOriginal[i] = false; + } else { + h->isOriginal[i] = true; + } + } + + h->next = hashhead; + if (hashhead) { + hashhead->prev = h; + } + h->prev = nullptr; + hashhead = h; + handle = h; +} + DynamicLoader::~DynamicLoader() { Release(); RunDestructors(); @@ -71,9 +402,6 @@ DynamicLoader::~DynamicLoader() { mpPatchAddresses32 = nullptr; } -// TODO where does this go? -static HashPointer *hashhead; - void DynamicLoader::Release() { if (handle) { HashPointer *h = reinterpret_cast(handle); From 3089530de1f13e7a0b11248578d0518380b94ee6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:06:36 +0100 Subject: [PATCH 051/372] 60.2%: rewrite mirror pose and qfast paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 437 ++++++++++++++---- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 149 +++++- 2 files changed, 465 insertions(+), 121 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index c685b9953..4b856a0d7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -175,36 +175,40 @@ FnDeltaQFast::~FnDeltaQFast() { void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { DeltaQFast *deltaQ = reinterpret_cast(anim); - int numBones = deltaQ->mNumBones; + unsigned char numBones = deltaQ->mNumBones; mpAnim = anim; - mBins = reinterpret_cast(deltaQ->GetMinRange()) + numBones * sizeof(DeltaQFastMinRange); + mBins = reinterpret_cast(deltaQ) + 0x14 + (numBones << 4); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = AlignSize2(numBones * (6 + ((deltaQ->GetBinLength() - 1) * 3))); - mPrevKey = -1; - mNextKey = -1; + mBinSize = AlignSize2(numBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); mBoneMask = nullptr; + mNextKey = -1; + mPrevKey = -1; if (numBones != 0) { - unsigned char *block = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * 0x40)); - DeltaQFastMinRange *minRange = deltaQ->GetMinRange(); + unsigned char *block = reinterpret_cast(MemoryPoolManager::NewBlock(numBones << 6)); + unsigned char *qBlock = block + (numBones << 5); + DeltaQFastMinRange *minRange = reinterpret_cast(deltaQ + 1); mMinRangesf = reinterpret_cast(block); - mPrevQBlock = block + numBones * sizeof(DeltaQFastMinRangef); - mPrevQs = reinterpret_cast(mPrevQBlock); - mNextQBlock = reinterpret_cast(mPrevQs) + numBones * sizeof(*mPrevQs); + mPrevQBlock = qBlock; + mPrevQs = reinterpret_cast(qBlock); + mNextQBlock = qBlock + (numBones << 4); mNextQs = reinterpret_cast(mNextQBlock); for (int ibone = 0; ibone < numBones; ibone++) { - mMinRangesf[ibone].mMin.x = minRange[ibone].mMin[0] * kQFastMinScale16 - 1.0f; - mMinRangesf[ibone].mMin.y = minRange[ibone].mMin[1] * kQFastMinScale16 - 1.0f; - mMinRangesf[ibone].mMin.z = minRange[ibone].mMin[2] * kQFastMinScale16 - 1.0f; - mMinRangesf[ibone].mMin.w = minRange[ibone].mMin[3] * kQFastMinScale16 - 1.0f; - mMinRangesf[ibone].mRange.x = minRange[ibone].mRange[0] * kQFastMinScale16; - mMinRangesf[ibone].mRange.y = minRange[ibone].mRange[1] * kQFastMinScale16; - mMinRangesf[ibone].mRange.z = minRange[ibone].mRange[2] * kQFastMinScale16; - mMinRangesf[ibone].mRange.w = minRange[ibone].mRange[3] * kQFastMinScale16; + unsigned short *minRangeValues = reinterpret_cast(&minRange[ibone]); + float *minRangefValues = reinterpret_cast(&mMinRangesf[ibone]); + + minRangefValues[0] = minRangeValues[0] * kQFastMinScale16 - 1.0f; + minRangefValues[1] = minRangeValues[1] * kQFastMinScale16 - 1.0f; + minRangefValues[2] = minRangeValues[2] * kQFastMinScale16 - 1.0f; + minRangefValues[3] = minRangeValues[3] * kQFastMinScale16 - 1.0f; + minRangefValues[4] = minRangeValues[4] * kQFastMinScale16; + minRangefValues[5] = minRangeValues[5] * kQFastMinScale16; + minRangefValues[6] = minRangeValues[6] * kQFastMinScale16; + minRangefValues[7] = minRangeValues[7] * kQFastMinScale16; } } } @@ -267,26 +271,43 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx return; } - int ceilBinIdx = ceilKey >> deltaQ->GetBinLengthPower(); - unsigned char *binData = GetQFastBin(mBins, mBinSize, ceilBinIdx); + int ceilBinIdx = ceilKey >> (deltaQ->mBinLengthPower & 0x3F); + unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; if (ceilBinIdx == floorBinIdx) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, floorDeltaIdx); - - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 delta; - - DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); - mNextQs[ibone].x = mPrevQs[ibone].x + delta.x; - mNextQs[ibone].y = mPrevQs[ibone].y + delta.y; - mNextQs[ibone].z = mPrevQs[ibone].z + delta.z; - mNextQs[ibone].w = mPrevQs[ibone].w + delta.w; + unsigned int numBones = deltaQ->mNumBones; + unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; + + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + nextQ[0] = minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0] + prevQ[0]; + nextQ[1] = minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1] + prevQ[1]; + nextQ[2] = minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2] + prevQ[2]; + nextQ[3] = + minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + + minRange[3] + prevQ[3]; + deltaData += 3; } } else { - DeltaQFastPhysical *physical = GetQFastPhysical(binData); - - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DecodeQFastPhysical(physical[ibone], mNextQs[ibone]); + unsigned int numBones = deltaQ->mNumBones; + unsigned short *physical = reinterpret_cast(binData); + + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + physical += 3; } } @@ -344,34 +365,55 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi return; } - int ceilBinIdx = ceilKey >> deltaQ->GetBinLengthPower(); - unsigned char *binData = GetQFastBin(mBins, mBinSize, ceilBinIdx); + int ceilBinIdx = ceilKey >> (deltaQ->mBinLengthPower & 0x3F); + unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; if (ceilBinIdx == floorBinIdx) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, floorDeltaIdx); + unsigned int numBones = deltaQ->mNumBones; + unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + unsigned char boneIdx = deltaQ->mBoneIdxs[ibone]; + if (!boneMask->GetBone(boneIdx)) { continue; } - UMath::Vector4 delta; - - DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); - mNextQs[ibone].x = mPrevQs[ibone].x + delta.x; - mNextQs[ibone].y = mPrevQs[ibone].y + delta.y; - mNextQs[ibone].z = mPrevQs[ibone].z + delta.z; - mNextQs[ibone].w = mPrevQs[ibone].w + delta.w; + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + nextQ[0] = minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0] + prevQ[0]; + nextQ[1] = minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1] + prevQ[1]; + nextQ[2] = minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2] + prevQ[2]; + nextQ[3] = + minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + + minRange[3] + prevQ[3]; + numBones = deltaQ->mNumBones; + deltaData += 3; } } else { - DeltaQFastPhysical *physical = GetQFastPhysical(binData); + unsigned int numBones = deltaQ->mNumBones; + unsigned short *physical = reinterpret_cast(binData); - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + unsigned char boneIdx = deltaQ->mBoneIdxs[ibone]; + if (!boneMask->GetBone(boneIdx)) { + physical += 3; continue; } - DecodeQFastPhysical(physical[ibone], mNextQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + physical += 3; } } @@ -395,67 +437,269 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } DeltaQFast *deltaQ = reinterpret_cast(mpAnim); - int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); - int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); - int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); - int prevBinIdx = mPrevKey >> deltaQ->GetBinLengthPower(); - bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); - unsigned char *binData = GetQFastBin(mBins, mBinSize, floorBinIdx); - DeltaQFastPhysical *floorPhys = GetQFastPhysical(binData); - int prevDeltaIdx; + float *quatBase = sqt + 4; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (deltaQ->mNumBones) { + unsigned int floorTime = static_cast(currTime); + unsigned short *times = deltaQ->mTimes; + unsigned int floorKey; + int prevKey = mPrevKey; + unsigned int binLengthPower; + unsigned int floorDeltaIdx; + int floorBinIdx; + unsigned int binLengthMask; + + if (!times) { + if (static_cast(floorTime) < 0) { + floorKey = 0; + } else { + floorKey = floorTime; + if (static_cast(deltaQ->mNumKeys) <= static_cast(floorTime)) { + floorKey = deltaQ->mNumKeys - 1; + } + } + } else { + if (static_cast(floorTime) < static_cast(times[0])) { + floorKey = 0; + } else { + int timeIndex = prevKey - 1; + if (prevKey < 1) { + timeIndex = 0; + } + + if (static_cast(floorTime) < static_cast(times[timeIndex])) { + if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { + break; + } + } while (static_cast(floorTime) < static_cast(times[timeIndex])); + } + } else { + int lastTimeIndex = deltaQ->mNumKeys - 2; + if (timeIndex < lastTimeIndex) { + unsigned int nextTime = times[timeIndex + 1]; + int nextIndex = timeIndex; + + while (((timeIndex = nextIndex), static_cast(nextTime) <= static_cast(floorTime) && + ((timeIndex = nextIndex + 1), timeIndex < lastTimeIndex))) { + nextTime = times[nextIndex + 2]; + nextIndex = timeIndex; + } + } + } + + floorKey = timeIndex + 1; + } + } - if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DecodeQFastPhysical(floorPhys[ibone], mPrevQs[ibone]); + binLengthPower = deltaQ->mBinLengthPower; + floorBinIdx = static_cast(floorKey) >> (binLengthPower & 0x3F); + binLengthMask = 0x7FFFFFFFU >> ((0x1F - binLengthPower) & 0x3F); + floorDeltaIdx = floorKey & binLengthMask; + + if (static_cast(mNextKey) == floorKey) { + UMath::Vector4 *swapQs = mPrevQs; + + mPrevQs = mNextQs; + mNextQs = swapQs; + mNextKey = prevKey; + } else { + int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; + unsigned int prevDeltaIdx = static_cast(mPrevKey); + + if (prevKey == static_cast(floorKey + 1)) { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + prevQ[0] = nextQ[0]; + prevQ[1] = nextQ[1]; + prevQ[2] = nextQ[2]; + prevQ[3] = nextQ[3]; + } + + mNextKey = prevKey; + } + + if (prevDeltaIdx == 0xFFFFFFFFU || floorBinIdx != (prevKey >> (binLengthPower & 0x3F)) || floorDeltaIdx == 0 || + (static_cast(floorKey) < prevKey && !IsReverseDeltaSumEnabled())) { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + unsigned short *physical = reinterpret_cast(binData + ibone * 6); + + prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = + static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + + prevDeltaIdx = 0; + } else { + prevDeltaIdx &= binLengthMask; + } + + if (static_cast(prevDeltaIdx) < static_cast(floorDeltaIdx)) { + unsigned char *deltaData = + reinterpret_cast(binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3); + + do { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + prevQ[0] = prevQ[0] + minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]; + prevQ[1] = prevQ[1] + minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]; + prevQ[2] = prevQ[2] + minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]; + prevQ[3] = prevQ[3] + + minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * + kQFastDeltaScale6 + + minRange[3]; + deltaData += 3; + } + prevDeltaIdx++; + } while (static_cast(prevDeltaIdx) < static_cast(floorDeltaIdx)); + } else if (static_cast(floorDeltaIdx) < static_cast(prevDeltaIdx)) { + unsigned char *deltaData = reinterpret_cast( + binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3 - 3); + + while ((prevDeltaIdx--), static_cast(floorDeltaIdx) <= static_cast(prevDeltaIdx)) { + for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + prevQ[0] = prevQ[0] - (minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]); + prevQ[1] = prevQ[1] - (minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]); + prevQ[2] = prevQ[2] - (minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]); + prevQ[3] = prevQ[3] - + (minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * + kQFastDeltaScale6 + + minRange[3]); + deltaData -= 3; + } + } + } } - prevDeltaIdx = 0; - } else { - prevDeltaIdx = mPrevKey & deltaQ->GetBinLengthModMask(); - } - if (prevDeltaIdx < floorDeltaIdx) { - AddDelta(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); - } else if (prevDeltaIdx > floorDeltaIdx) { - SubDelta(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); - } + mPrevKey = floorKey; + times = deltaQ->mTimes; - int ceilKey = floorKey + 1; + if (!times) { + float floorTimef = static_cast(static_cast(currTime)); - if (ceilKey >= deltaQ->mNumKeys) { - ceilKey = floorKey; - } + if (currTime != floorTimef && static_cast(floorKey) < deltaQ->mNumKeys - 1) { + float t = currTime - floorTimef; - if (ceilKey > floorKey) { - UpdateNextQs(deltaQ, ceilKey, floorBinIdx, floorDeltaIdx); - float t = ComputeQFastBlendT(deltaQ, floorKey, ceilKey, currTime); + UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - FastQuatBlendF4(t, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&mNextQs[ibone]), - GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone])); + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + unsigned char boneIdx = boneIdxs[ibone]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; + } + + goto finish_const_bones; + } + } else if (floorKey == 0) { + if (currTime != 0.0f && static_cast(floorKey) < deltaQ->mNumKeys - 1) { + float t = currTime / static_cast(times[0]); + + UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); + + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + unsigned char boneIdx = boneIdxs[ibone]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; + } + + goto finish_const_bones; + } + } else { + float floorTimef = static_cast(times[floorKey - 1]); + + if (currTime != floorTimef && static_cast(floorKey) < deltaQ->mNumKeys - 1) { + float t = (currTime - floorTimef) / (static_cast(times[floorKey]) - floorTimef); + + UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); + + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + unsigned char boneIdx = boneIdxs[ibone]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; + } + + goto finish_const_bones; + } } - } else { - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - float *out = GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone]); - out[0] = mPrevQs[ibone].x; - out[1] = mPrevQs[ibone].y; - out[2] = mPrevQs[ibone].z; - out[3] = mPrevQs[ibone].w; + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *out = reinterpret_cast(boneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); + + out[0] = prevQ[0]; + out[1] = prevQ[1]; + out[2] = prevQ[2]; + out[3] = prevQ[3]; } } - for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { - UMath::Vector4 q; - float *out = GetQFastOutputQuat(sqt, mConstBoneIdxs[ibone]); +finish_const_bones: + for (int ibone = 0; ibone < static_cast(deltaQ->mNumConstBones); ibone++) { + unsigned short *physical = reinterpret_cast(reinterpret_cast(mConstPhysical) + ibone * 6); + float *out = reinterpret_cast(mConstBoneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); - DecodeQFastPhysical(mConstPhysical[ibone], q); - out[0] = q.x; - out[1] = q.y; - out[2] = q.z; - out[3] = q.w; + out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } - mPrevKey = floorKey; return true; } @@ -549,7 +793,6 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM out[3] = q.w; } - mPrevKey = floorKey; return true; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index ad756af0e..c1a222cf6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -136,33 +136,134 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { } void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const BoneMask *mask) { - for (int ibone = 0; ibone < GetNumBones(); ibone++) { - if (!mask || mask->GetBone(ibone)) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + int numBones = GetNumBones(); + + if (!mask) { + if (pose == mirrorPose) { + for (int ibone = 0; ibone < numBones; ibone++) { + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; - if (mirrorBone < 0 || mirrorBone >= GetNumBones()) { - mirrorBone = ibone; + if (ibone < mirrorBone) { + float *dst = &mirrorPose[mirrorBone * 12]; + float *src = &mirrorPose[ibone * 12]; + float value = dst[4]; + + dst[4] = -src[4]; + src[4] = -value; + value = dst[5]; + dst[5] = -src[5]; + src[5] = -value; + value = dst[6]; + dst[6] = src[6]; + src[6] = value; + value = dst[7]; + dst[7] = src[7]; + src[7] = value; + value = dst[8]; + dst[8] = src[8]; + src[8] = value; + value = dst[9]; + dst[9] = src[9]; + src[9] = value; + value = dst[10]; + dst[10] = -src[10]; + src[10] = -value; + } else if (mirrorBone == ibone) { + float *dst = &mirrorPose[ibone * 12]; + + dst[4] = -dst[4]; + dst[5] = -dst[5]; + dst[10] = -dst[10]; + } } + } else { + for (int ibone = 0; ibone < numBones; ibone++) { + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + float *src = &pose[ibone * 12]; + float *dst = &mirrorPose[mirrorBone * 12]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = -src[5]; + dst[6] = -src[6]; + dst[7] = src[7]; + dst[8] = -src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; - float *dst = &mirrorPose[ibone * 12]; - const float *src = &pose[mirrorBone * 12]; - - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - dst[4] = src[4]; - dst[5] = -src[5]; - dst[6] = -src[6]; - dst[7] = src[7]; - dst[8] = -src[8]; - dst[9] = src[9]; - dst[10] = src[10]; - dst[11] = src[11]; - - if (!local) { - dst[4] = -dst[4]; - dst[7] = -dst[7]; + if (!local) { + dst[4] = -dst[4]; + dst[7] = -dst[7]; + } + } + } + } else if (pose == mirrorPose) { + for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { + if (mask->GetBone(ibone)) { + unsigned int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; + + if (ibone < mirrorBone) { + float *dst = &mirrorPose[mirrorBone * 12]; + float *src = &mirrorPose[ibone * 12]; + float value = dst[4]; + + dst[4] = -src[4]; + src[4] = -value; + value = dst[5]; + dst[5] = -src[5]; + src[5] = -value; + value = dst[6]; + dst[6] = src[6]; + src[6] = value; + value = dst[7]; + dst[7] = src[7]; + src[7] = value; + value = dst[8]; + dst[8] = src[8]; + src[8] = value; + value = dst[9]; + dst[9] = src[9]; + src[9] = value; + value = dst[10]; + dst[10] = -src[10]; + src[10] = -value; + } else if (mirrorBone == ibone) { + float *dst = &mirrorPose[ibone * 12]; + + dst[4] = -dst[4]; + dst[5] = -dst[5]; + dst[10] = -dst[10]; + } + } + } + } else { + for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { + if (mask->GetBone(ibone)) { + int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; + float *src = &pose[ibone * 12]; + float *dst = &mirrorPose[mirrorBone * 12]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = -src[5]; + dst[6] = -src[6]; + dst[7] = src[7]; + dst[8] = -src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + + if (!local) { + dst[4] = -dst[4]; + dst[7] = -dst[7]; + } } } } From e37aa439bd845f9423b92d39bd33f90db87254b2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:14:25 +0100 Subject: [PATCH 052/372] 60.3%: improve pose blender transition blending Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 1afc1d6b2..81421c82e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -219,30 +219,61 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask return false; } - if (!mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], sqtBuffer, boneMask)) { + if (!mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { return false; } - if (mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { - EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, sqtBuffer, mAlignRootBoneIdx); - } - int numBones = mpSkel->GetNumBones(); - int transBoneIdx = mAlignRootBoneIdx < 0 ? 0 : mAlignRootBoneIdx; + if (!boneMask) { + if (mAlignRootBoneIdx < 0) { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + int poseIdx = boneIdx * 12; - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - if (boneMask && !boneMask->GetBone(boneIdx)) { - continue; + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); + } else { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneIdx == mAlignRootBoneIdx) { + EAGL4::Transform rootTransform; + + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + } + + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } } + } else if (mAlignRootBoneIdx < 0) { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + int poseIdx = boneIdx * 12; - int poseIdx = boneIdx * 12; + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + } - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &sqtBuffer[poseIdx + 4], &sqtBuffer[poseIdx + 4]); - } + if (boneMask->GetBone(0)) { + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); + } + } else { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + if (boneIdx == mAlignRootBoneIdx) { + EAGL4::Transform rootTransform; + + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + } - if (!boneMask || boneMask->GetBone(transBoneIdx)) { - BlendRootTranslation(w, mPose[0], sqtBuffer, sqtBuffer, transBoneIdx); + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + } } return true; From 86f11c958f852bf1c8038ac95b63845fa50355f9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:17:32 +0100 Subject: [PATCH 053/372] 61.4%: tighten pose blender evalsqt flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 96 ++++++++++--------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 81421c82e..ad756b598 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -191,18 +191,23 @@ void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask *boneMask) { if (currentTime <= mStartTransTime) { - return mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], sqtBuffer, boneMask); + if (mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], sqtBuffer, boneMask)) { + return true; + } + return false; } if (currentTime >= mEndTransTime) { bool result = mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], sqtBuffer, boneMask); - if (result && mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { - EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, sqtBuffer, mAlignRootBoneIdx); + if (result) { + if (mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { + EAGL4::Transform rootTransform; + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, sqtBuffer, mAlignRootBoneIdx); + } + return true; } - - return result; + return false; } float w = (currentTime - mStartTransTime) / mDuration; @@ -219,64 +224,63 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask return false; } - if (!mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { - return false; - } - int numBones = mpSkel->GetNumBones(); - if (!boneMask) { - if (mAlignRootBoneIdx < 0) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - int poseIdx = boneIdx * 12; + if (mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { + if (!boneMask) { + if (mAlignRootBoneIdx >= 0) { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneIdx == mAlignRootBoneIdx) { + EAGL4::Transform rootTransform; - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); - } + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + } - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); - } else { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - if (boneIdx == mAlignRootBoneIdx) { - EAGL4::Transform rootTransform; + int poseIdx = boneIdx * 12; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); } + } else { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + int poseIdx = boneIdx * 12; - int poseIdx = boneIdx * 12; + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); } - } - } else if (mAlignRootBoneIdx < 0) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - if (boneMask->GetBone(boneIdx)) { - int poseIdx = boneIdx * 12; + } else if (mAlignRootBoneIdx >= 0) { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + if (boneIdx == mAlignRootBoneIdx) { + EAGL4::Transform rootTransform; - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); - } - } + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + } - if (boneMask->GetBone(0)) { - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); - } - } else { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - if (boneMask->GetBone(boneIdx)) { - if (boneIdx == mAlignRootBoneIdx) { - EAGL4::Transform rootTransform; + int poseIdx = boneIdx * 12; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); } + } + } else { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + int poseIdx = boneIdx * 12; - int poseIdx = boneIdx * 12; + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + } - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + if (boneMask->GetBone(0)) { + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); } } + return true; } - return true; + return false; } void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPose) { From 9177984a0ecd4fec9deec981dd7bf567b5fbe1ba Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:18:22 +0100 Subject: [PATCH 054/372] 61.8%: recover pose blender eval flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index ad756b598..b92379f5c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -311,23 +311,31 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos } mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], mPose[0]); - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], outputPose); + int numBones = mpSkel->GetNumBones(); + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], mPose[1]); if (mAlignRootBoneIdx >= 0) { - EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, outputPose, mAlignRootBoneIdx); - } + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneIdx == mAlignRootBoneIdx) { + EAGL4::Transform rootTransform; - int numBones = mpSkel->GetNumBones(); - int transBoneIdx = mAlignRootBoneIdx < 0 ? 0 : mAlignRootBoneIdx; + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], outputPose, boneIdx); + } - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - int poseIdx = boneIdx * 12; + int poseIdx = boneIdx * 12; - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &outputPose[poseIdx + 4], &outputPose[poseIdx + 4]); - } + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); + } + } else { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + int poseIdx = boneIdx * 12; - BlendRootTranslation(w, mPose[0], outputPose, outputPose, transBoneIdx); + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); + } + + BlendRootTranslation(w, mPose[0], mPose[1], outputPose, 0); + } } }; // namespace EAGL4Anim From 051d8ba24e459d9e39f2b4412d1d08e7e84ea8ef Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:32:17 +0100 Subject: [PATCH 055/372] 63.7%: recover run blender root blending Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 123 +++++++++++++----- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 34 ++++- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index ff34df932..d56fda3d2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -5,6 +5,8 @@ #include "PhaseChan.h" #include "ScratchBuffer.h" +#include + float length(float *v) { return sqrtf(v[0] * v[0] + v[1] * v[1]); } @@ -121,32 +123,75 @@ void FnRunBlender::SetWeight(float w) { mWeight = w - static_cast(idx); if (idx == mIdx) { - float denom = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; - - if (denom != 0.0f) { - float prevFreq = mFreq; + float prevFreq = mFreq; - mFreq = denom; - mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; - } + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; return; } - if (idx >= 0 && idx < mNumAnims) { + if (idx == mIdx + 1) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); + mFnAnims[0] = mFnAnims[1]; + mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx + 1]))); + } else if (idx == mIdx - 1) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); + mFnAnims[1] = mFnAnims[0]; + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx]))); + } else { + if (mFnAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); + } + if (mFnAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); + } + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx]))); mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx + 1]))); + } + + if (mVels) { + if (idx == mIdx + 1) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); + mFnVelAnims[0] = mFnVelAnims[1]; + mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx + 1]))); + } else if (idx == mIdx - 1) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); + mFnVelAnims[1] = mFnVelAnims[0]; + mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx]))); + } else { + if (mFnVelAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); + } + if (mFnVelAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); + } - if (mVels) { mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx]))); mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx + 1]))); } + } + + const PhaseChan *phase0 = mPhases[idx]; + const PhaseChan *phase1 = mPhases[idx + 1]; + float prevFreq = mFreq; + + if (!phase0->StartWithRight()) { + mAlignFrame[0] = static_cast(phase0->mStartTime + phase0->mCycles[0]); + } else { + mAlignFrame[0] = static_cast(phase0->mStartTime); + } - mIdx = idx; - mCycles[0] = 1.0f; - mCycles[1] = 1.0f; - mFreq = 1.0f; - mOffset = 0.0f; + mAlignFrame[1] = static_cast(phase1->mStartTime); + if (!phase1->StartWithRight()) { + mAlignFrame[1] += static_cast(phase1->mCycles[0]); } + + mIdx = idx; + mCycles[0] = static_cast(phase0->mCycles[0] + phase0->mCycles[1]) * 0.5f; + mCycles[1] = static_cast(phase1->mCycles[0] + phase1->mCycles[1]) * 0.5f; + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; } bool FnRunBlender::EvalPhase(float currTime, PhaseValue &phase) { @@ -211,9 +256,14 @@ bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { return false; } - f[0] = 0.0f; - f[1] = 0.0f; - return false; + UMath::Vector4 q; + + ComputeRootQ(t0, t1, q); + + f[0] = 2.0f * (q.w * q.z - q.x * q.y); + f[1] = 2.0f * (q.y * q.z + q.x * q.w); + printf("Facing: %g %g\n", f[0], f[1]); + return true; } float FnRunBlender::GetFrequency() const { @@ -221,24 +271,39 @@ float FnRunBlender::GetFrequency() const { } void FnRunBlender::ComputeBeginRootQ(UMath::Vector4 &q) const { - q.x = 0.0f; - q.y = 0.0f; - q.z = 0.0f; - q.w = 1.0f; + ComputeRootQ(0.0f, 0.0f, q); } void FnRunBlender::ComputeEndRootQ(UMath::Vector4 &q) const { - q.x = 0.0f; - q.y = 0.0f; - q.z = 0.0f; - q.w = 1.0f; + int idx = mIdx * 4; + float t0 = static_cast(mPhases[mIdx]->mNumFrames - 1); + float t1 = static_cast(mPhases[mIdx + 1]->mNumFrames - 1); + + ComputeRootQ(t0, t1, q); } void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { - q.x = 0.0f; - q.y = 0.0f; - q.z = 0.0f; - q.w = 1.0f; + ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); + float *pose = reinterpret_cast(scratch.GetBuffer()); + + if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, pose, 0)) { + return; + } + + UMath::Vector4 q0 = *reinterpret_cast(&pose[4]); + + if (mWeight == 0.0f) { + q = q0; + return; + } + + mSkeleton->GetStillPose(pose, 0); + if (!mFnAnims[1] || !mFnAnims[1]->EvalSQT(t1, pose, 0)) { + return; + } + + UMath::Vector4 q1 = *reinterpret_cast(&pose[4]); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); } float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 1f198ee2f..68b77b7ec 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -1,8 +1,12 @@ #include "FnTurnBlender.h" +#include "AnimUtil.h" #include "FnPoseBlender.h" +#include "FnRunBlender.h" #include "ScratchBuffer.h" +#include + namespace EAGL4Anim { @@ -198,9 +202,18 @@ bool FnTurnBlender::BlendBeginFacing(float *f) const { return false; } - f[0] = 0.0f; - f[1] = 0.0f; - return false; + UMath::Vector4 q0; + UMath::Vector4 q1; + UMath::Vector4 q; + + reinterpret_cast(mFnAnims[0])->ComputeBeginRootQ(q0); + reinterpret_cast(mFnAnims[1])->ComputeBeginRootQ(q1); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + + f[0] = 2.0f * (q.w * q.z - q.x * q.y); + f[1] = 2.0f * (q.y * q.z + q.x * q.w); + printf("Facing: %g %g\n", f[0], f[1]); + return true; } bool FnTurnBlender::BlendEndFacing(float *f) const { @@ -208,9 +221,18 @@ bool FnTurnBlender::BlendEndFacing(float *f) const { return false; } - f[0] = 0.0f; - f[1] = 0.0f; - return false; + UMath::Vector4 q0; + UMath::Vector4 q1; + UMath::Vector4 q; + + reinterpret_cast(mFnAnims[0])->ComputeEndRootQ(q0); + reinterpret_cast(mFnAnims[1])->ComputeEndRootQ(q1); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + + f[0] = 2.0f * (q.w * q.z - q.x * q.y); + f[1] = 2.0f * (q.y * q.z + q.x * q.w); + printf("Facing: %g %g\n", f[0], f[1]); + return true; } }; // namespace EAGL4Anim From ab10e96f985a3aea9fff2a90497ec7685bb147c7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:42:05 +0100 Subject: [PATCH 056/372] 65.6%: inline delta singleq setup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 112 +++++++++++++++++- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 22 +++- 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 67a5e10d5..ec2f58148 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -189,11 +189,61 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas if (boneMask) { return EvalSQTMasked(currTime, boneMask, sqt); } + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + if (!mPrevQs) { - InitBuffersAsRequired(); - } + mMinRanges = GetSingleQMinRanges(deltaQ); + mBins = GetSingleQBinStart(deltaQ); + mBinSize = GetSingleQBinSize(deltaQ); - DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + if (deltaQ->mNumBones != 0) { + float eul[3]; + + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + mPreMultQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); + mPostMultQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaSingleQMinRangef minRangef; + + DecodeSingleQMinRange(mMinRanges[ibone], minRangef); + + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; + + if (minRangef.mIndex == 0) { + eul[0] = kSingleQFloatZero; + eul[1] = minRangef.mConst0; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (minRangef.mIndex == 1) { + eul[0] = minRangef.mConst0; + eul[1] = kSingleQFloatZero; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + + eul[0] = kSingleQFloatZero; + eul[1] = kSingleQFloatZero; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else { + eul[0] = minRangef.mConst0; + eul[1] = minRangef.mConst1; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + } + } + } + } int floorTime = FloatToInt(currTime); int floorKey; @@ -346,11 +396,61 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas } bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + if (!mPrevQs) { - InitBuffersAsRequired(); - } + mMinRanges = GetSingleQMinRanges(deltaQ); + mBins = GetSingleQBinStart(deltaQ); + mBinSize = GetSingleQBinSize(deltaQ); - DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + if (deltaQ->mNumBones != 0) { + float eul[3]; + + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + mPreMultQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); + mPostMultQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaSingleQMinRangef minRangef; + + DecodeSingleQMinRange(mMinRanges[ibone], minRangef); + + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; + + if (minRangef.mIndex == 0) { + eul[0] = kSingleQFloatZero; + eul[1] = minRangef.mConst0; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (minRangef.mIndex == 1) { + eul[0] = minRangef.mConst0; + eul[1] = kSingleQFloatZero; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + + eul[0] = kSingleQFloatZero; + eul[1] = kSingleQFloatZero; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else { + eul[0] = minRangef.mConst0; + eul[1] = minRangef.mConst1; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + } + } + } + } int floorTime = FloatToInt(currTime); int floorKey; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index d56fda3d2..b9cb535ae 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -256,9 +256,28 @@ bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { return false; } + ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); + float *pose = reinterpret_cast(scratch.GetBuffer()); + UMath::Vector4 q0; UMath::Vector4 q; - ComputeRootQ(t0, t1, q); + if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, pose, 0)) { + return false; + } + + q0 = *reinterpret_cast(&pose[4]); + + if (mWeight == 0.0f) { + q = q0; + } else { + mSkeleton->GetStillPose(pose, 0); + if (!mFnAnims[1] || !mFnAnims[1]->EvalSQT(t1, pose, 0)) { + return false; + } + + UMath::Vector4 q1 = *reinterpret_cast(&pose[4]); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + } f[0] = 2.0f * (q.w * q.z - q.x * q.y); f[1] = 2.0f * (q.y * q.z + q.x * q.w); @@ -275,7 +294,6 @@ void FnRunBlender::ComputeBeginRootQ(UMath::Vector4 &q) const { } void FnRunBlender::ComputeEndRootQ(UMath::Vector4 &q) const { - int idx = mIdx * 4; float t0 = static_cast(mPhases[mIdx]->mNumFrames - 1); float t1 = static_cast(mPhases[mIdx + 1]->mNumFrames - 1); From 00390bf4b042bc721741a9688d8930da81955771 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:47:58 +0100 Subject: [PATCH 057/372] 65.6%: improve delta qfast floor keys Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 4b856a0d7..0e476bb01 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -432,8 +432,8 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } if (mBoneMask) { mBoneMask = nullptr; - mPrevKey = -1; mNextKey = -1; + mPrevKey = -1; } DeltaQFast *deltaQ = reinterpret_cast(mpAnim); @@ -441,9 +441,9 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (deltaQ->mNumBones) { - unsigned int floorTime = static_cast(currTime); + int floorTime = FloatToInt(currTime); unsigned short *times = deltaQ->mTimes; - unsigned int floorKey; + int floorKey; int prevKey = mPrevKey; unsigned int binLengthPower; unsigned int floorDeltaIdx; @@ -451,16 +451,16 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned int binLengthMask; if (!times) { - if (static_cast(floorTime) < 0) { + if (floorTime < 0) { floorKey = 0; } else { floorKey = floorTime; - if (static_cast(deltaQ->mNumKeys) <= static_cast(floorTime)) { + if (deltaQ->mNumKeys <= floorTime) { floorKey = deltaQ->mNumKeys - 1; } } } else { - if (static_cast(floorTime) < static_cast(times[0])) { + if (floorTime < times[0]) { floorKey = 0; } else { int timeIndex = prevKey - 1; @@ -468,14 +468,14 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) timeIndex = 0; } - if (static_cast(floorTime) < static_cast(times[timeIndex])) { + if (floorTime < times[timeIndex]) { if (timeIndex > 0) { do { timeIndex--; if (timeIndex < 1) { break; } - } while (static_cast(floorTime) < static_cast(times[timeIndex])); + } while (floorTime < times[timeIndex]); } } else { int lastTimeIndex = deltaQ->mNumKeys - 2; @@ -483,7 +483,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned int nextTime = times[timeIndex + 1]; int nextIndex = timeIndex; - while (((timeIndex = nextIndex), static_cast(nextTime) <= static_cast(floorTime) && + while (((timeIndex = nextIndex), static_cast(nextTime) <= floorTime && ((timeIndex = nextIndex + 1), timeIndex < lastTimeIndex))) { nextTime = times[nextIndex + 2]; nextIndex = timeIndex; @@ -500,7 +500,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) binLengthMask = 0x7FFFFFFFU >> ((0x1F - binLengthPower) & 0x3F); floorDeltaIdx = floorKey & binLengthMask; - if (static_cast(mNextKey) == floorKey) { + if (mNextKey == floorKey) { UMath::Vector4 *swapQs = mPrevQs; mPrevQs = mNextQs; @@ -508,7 +508,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mNextKey = prevKey; } else { int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; - unsigned int prevDeltaIdx = static_cast(mPrevKey); + int prevDeltaIdx = mPrevKey; if (prevKey == static_cast(floorKey + 1)) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { @@ -524,8 +524,8 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mNextKey = prevKey; } - if (prevDeltaIdx == 0xFFFFFFFFU || floorBinIdx != (prevKey >> (binLengthPower & 0x3F)) || floorDeltaIdx == 0 || - (static_cast(floorKey) < prevKey && !IsReverseDeltaSumEnabled())) { + if (prevDeltaIdx == -1 || floorBinIdx != (prevKey >> (binLengthPower & 0x3F)) || floorDeltaIdx == 0 || + (floorKey < prevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); unsigned short *physical = reinterpret_cast(binData + ibone * 6); @@ -541,10 +541,10 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) prevDeltaIdx = 0; } else { - prevDeltaIdx &= binLengthMask; + prevDeltaIdx &= static_cast(binLengthMask); } - if (static_cast(prevDeltaIdx) < static_cast(floorDeltaIdx)) { + if (prevDeltaIdx < static_cast(floorDeltaIdx)) { unsigned char *deltaData = reinterpret_cast(binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3); @@ -566,12 +566,12 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) deltaData += 3; } prevDeltaIdx++; - } while (static_cast(prevDeltaIdx) < static_cast(floorDeltaIdx)); - } else if (static_cast(floorDeltaIdx) < static_cast(prevDeltaIdx)) { + } while (prevDeltaIdx < static_cast(floorDeltaIdx)); + } else if (static_cast(floorDeltaIdx) < prevDeltaIdx) { unsigned char *deltaData = reinterpret_cast( binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3 - 3); - while ((prevDeltaIdx--), static_cast(floorDeltaIdx) <= static_cast(prevDeltaIdx)) { + while ((prevDeltaIdx--), static_cast(floorDeltaIdx) <= prevDeltaIdx) { for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); float *minRange = reinterpret_cast(&mMinRangesf[ibone]); @@ -596,7 +596,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) times = deltaQ->mTimes; if (!times) { - float floorTimef = static_cast(static_cast(currTime)); + float floorTimef = static_cast(floorTime); if (currTime != floorTimef && static_cast(floorKey) < deltaQ->mNumKeys - 1) { float t = currTime - floorTimef; From 8a905106597d0bd59d324f62d557ee47b7c4236f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 22:49:07 +0100 Subject: [PATCH 058/372] 65.9%: inline delta q setup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 34 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index cc9b0a889..d2a7be0de 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -157,11 +157,22 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (boneMask) { return EvalSQTMasked(currTime, boneMask, sqt); } + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + if (!mBins) { - InitBuffersAsRequired(); - } + unsigned char numBones = deltaQ->mNumBones; - DeltaQ *deltaQ = reinterpret_cast(mpAnim); + mMinRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); + mBins = reinterpret_cast(mMinRanges) + numBones * sizeof(DeltaQMinRange); + mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); + mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); + mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); + + if (numBones != 0) { + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + } + } int floorTime = FloatToInt(currTime); int floorKey; @@ -330,11 +341,22 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { } bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + if (!mBins) { - InitBuffersAsRequired(); - } + unsigned char numBones = deltaQ->mNumBones; - DeltaQ *deltaQ = reinterpret_cast(mpAnim); + mMinRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); + mBins = reinterpret_cast(mMinRanges) + numBones * sizeof(DeltaQMinRange); + mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); + mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); + mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); + + if (numBones != 0) { + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + } + } int floorTime = FloatToInt(currTime); int floorKey; From 61a27b4df1fd5d5e04b8a0841f1c8a8a98808fb2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:00:59 +0100 Subject: [PATCH 059/372] 66.3%: inline stateless f3 unmasked eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index d31762514..9bd325956 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -119,7 +119,55 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask if (mBoneMask) { mBoneMask = nullptr; } - EvalSQTfast(currTime, sqt, nullptr, slerpReqd, floorKey, scale); + + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; + + if (!slerpReqd) { + for (int ibone = 0; ibone < nBones; ibone++) { + int index = dofIdxs[ibone]; + + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } + } else { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector3 prev; + UMath::Vector3 next; + int index = dofIdxs[ibone]; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; + sqt[index + 1] = prev.y + (next.y - prev.y) * scale; + sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + + frameData += 3; + nextFrameData += 3; + } + } + + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + int index = constIdxs[ibone]; + + sqt[index + 0] = constBuf[0]; + sqt[index + 1] = constBuf[1]; + sqt[index + 2] = constBuf[2]; + constBuf += 3; + } + } } else { EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); } From c29862e213cdd992cad91fec690216135840845a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:03:02 +0100 Subject: [PATCH 060/372] 66.6%: recover run blender match time Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index b9cb535ae..558502cf2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -370,11 +370,53 @@ void FnRunBlender::AlignRootQ(float *sqt) const {} void FnRunBlender::AlignVel(float *vel) const {} bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { - if (!mPhases || mNumAnims <= 0) { - return false; + PhaseValue phase; + float cycleLength = mWeight * (mCycles[1] - mCycles[0]) + mCycles[0]; + int searchCount = static_cast(cycleLength + cycleLength); + + phase.mAngle = 0.0f; + if (0.0f < input.mSearchLength && input.mSearchLength < static_cast(searchCount)) { + searchCount = static_cast(input.mSearchLength) + 1; + } + + const_cast(this)->EvalPhase(0.0f, phase); + + float bestDelta = input.mAngle - phase.mAngle; + if (bestDelta < 0.0f) { + bestDelta = -bestDelta; + } + + int bestIdx = 0; + for (int i = 1; i < searchCount; i++) { + float prevAngle = phase.mAngle; + + const_cast(this)->EvalPhase(static_cast(i), phase); + + if (prevAngle <= input.mAngle && input.mAngle <= phase.mAngle) { + float deltaAngle = phase.mAngle - prevAngle; + + if (0.0f <= deltaAngle * input.mDAngle) { + if (deltaAngle == 0.0f) { + time = static_cast(i - 1) + 0.5f; + } else { + time = (input.mAngle - prevAngle) / deltaAngle + static_cast(i - 1); + } + return true; + } + } + + float delta = input.mAngle - phase.mAngle; + if (delta < 0.0f) { + delta = -delta; + } + if (delta < bestDelta) { + bestIdx = i; + bestDelta = delta; + } } - return mPhases[0]->FindMatchTime(input, time); + time = static_cast(bestIdx); + return true; } }; // namespace EAGL4Anim From 2a620131c9d48e02da56cd3cc8902e56d64166b3 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:10:12 +0100 Subject: [PATCH 061/372] 66.9%: recover run blender align helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 558502cf2..3f8d00c7a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -350,10 +350,17 @@ int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const } void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { + float dot = v1[0] * v2[0] + v1[1] * v2[1]; + float norm = length(v1) * length(v2); + float w = ((dot / norm) + 1.0f) * 0.5f; + q.x = 0.0f; - q.y = 0.0f; q.z = 0.0f; - q.w = 1.0f; + q.y = sqrtf(1.0f - w); + q.w = sqrtf(w); + if (0.0f < v1[0] * v2[1] - v1[1] * v2[0]) { + q.y = -q.y; + } } void FnRunBlender::AlignCycleBeginEnd(int cIdx) { @@ -365,9 +372,38 @@ void FnRunBlender::AlignCycleBeginEnd(int cIdx) { mAlignQ.w = 1.0f; } -void FnRunBlender::AlignRootQ(float *sqt) const {} +void FnRunBlender::AlignRootQ(float *sqt) const { + float x = sqt[4]; + float y = sqt[5]; + float z = sqt[6]; + float w = sqt[7]; + float newX = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); + float newY = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; + float newZ = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; + float newW = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; + + sqt[4] = newX; + sqt[5] = newY; + sqt[6] = newZ; + sqt[7] = newW; +} -void FnRunBlender::AlignVel(float *vel) const {} +void FnRunBlender::AlignVel(float *vel) const { + float x = mAlignQ.x; + float y = mAlignQ.y; + float z = mAlignQ.z; + float w = mAlignQ.w; + float doubleY = y + y; + float doubleZ = z + z; + float oldX = vel[0]; + float oldY = vel[1]; + float y2 = y * doubleY; + float xz = x * doubleZ; + float wy = w * doubleY; + + vel[0] = oldX * (1.0f - (z * doubleZ + y2)) + oldY * (xz + wy); + vel[1] = oldX * (xz - wy) + oldY * (1.0f - (x * (x + x) + y2)); +} bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { PhaseValue phase; From 4764daf9903c5dda764da25991972bd7f0cfad87 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:11:33 +0100 Subject: [PATCH 062/372] 67.2%: recover turn blender align helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 68b77b7ec..6c0ca62e4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -7,6 +7,10 @@ #include +float turnLength(float *v) { + return sqrtf(v[0] * v[0] + v[1] * v[1]); +} + namespace EAGL4Anim { @@ -178,10 +182,17 @@ int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) cons } void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { + float dot = v1[0] * v2[0] + v1[1] * v2[1]; + float norm = turnLength(v1) * turnLength(v2); + float w = ((dot / norm) + 1.0f) * 0.5f; + q.x = 0.0f; - q.y = 0.0f; q.z = 0.0f; - q.w = 1.0f; + q.y = sqrtf(1.0f - w); + q.w = sqrtf(w); + if (0.0f < v1[0] * v2[1] - v1[1] * v2[0]) { + q.y = -q.y; + } } void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { @@ -193,9 +204,38 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { mAlignQ.w = 1.0f; } -void FnTurnBlender::AlignRootQ(float *sqt) const {} +void FnTurnBlender::AlignRootQ(float *sqt) const { + float x = sqt[4]; + float y = sqt[5]; + float z = sqt[6]; + float w = sqt[7]; + float newX = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); + float newY = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; + float newZ = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; + float newW = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; + + sqt[4] = newX; + sqt[5] = newY; + sqt[6] = newZ; + sqt[7] = newW; +} -void FnTurnBlender::AlignVel(float *vel) const {} +void FnTurnBlender::AlignVel(float *vel) const { + float x = mAlignQ.x; + float y = mAlignQ.y; + float z = mAlignQ.z; + float w = mAlignQ.w; + float doubleY = y + y; + float doubleZ = z + z; + float oldX = vel[0]; + float oldY = vel[1]; + float y2 = y * doubleY; + float xz = x * doubleZ; + float wy = w * doubleY; + + vel[0] = oldX * (1.0f - (z * doubleZ + y2)) + oldY * (xz + wy); + vel[1] = oldX * (xz - wy) + oldY * (1.0f - (x * (x + x) + y2)); +} bool FnTurnBlender::BlendBeginFacing(float *f) const { if (!f) { From a693a361aa1cd591818ba8a8e51276321883d5ff Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:15:18 +0100 Subject: [PATCH 063/372] 67.3%: reorder stateless q eval paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 3d16902a4..5f9943f57 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -127,9 +127,7 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mPrevKey = static_cast(floorKey); - if (boneMask) { - EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); - } else { + if (!boneMask) { if (mBoneMask) { mBoneMask = nullptr; } @@ -139,17 +137,7 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; - if (!slerpReqd) { - for (int ibone = 0; ibone < nBones; ibone++) { - float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); - - q[0] = UncompressStatelessQValue(frameData[0]); - q[1] = UncompressStatelessQValue(frameData[1]); - q[2] = UncompressStatelessQValue(frameData[2]); - q[3] = UncompressStatelessQValue(frameData[3]); - frameData += 4; - } - } else { + if (slerpReqd) { unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { @@ -168,6 +156,16 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) frameData += 4; nextFrameData += 4; } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); + + q[0] = UncompressStatelessQValue(frameData[0]); + q[1] = UncompressStatelessQValue(frameData[1]); + q[2] = UncompressStatelessQValue(frameData[2]); + q[3] = UncompressStatelessQValue(frameData[3]); + frameData += 4; + } } if (statelessQ->mNumConstBones != 0) { @@ -184,6 +182,8 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) constBuf += 4; } } + } else { + EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); } if (statelessQ->mF3Ptr) { From 6186fdb34a932df693dd2dd539303b81e5a61df5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:16:31 +0100 Subject: [PATCH 064/372] 67.5%: reorder stateless f3 eval loops Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 9bd325956..109c04685 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -126,16 +126,7 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); int nBones = statelessF3->mNumBones; - if (!slerpReqd) { - for (int ibone = 0; ibone < nBones; ibone++) { - int index = dofIdxs[ibone]; - - sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; - sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; - sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; - frameData += 3; - } - } else { + if (slerpReqd) { short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { @@ -153,6 +144,15 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask frameData += 3; nextFrameData += 3; } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + int index = dofIdxs[ibone]; + + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } } if (statelessF3->mNumConstBones != 0) { From 09f52be8e464c1a7b8c4d7b73b21681dc81bd741 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:18:26 +0100 Subject: [PATCH 065/372] 68.1%: reorder stateless masked eval paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 36 +++++++++--------- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 38 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 109c04685..d29cee6a9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -176,6 +176,10 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask } bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + } + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); short *dataBuf = statelessF3->GetData(); StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); @@ -184,28 +188,11 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo int nBones = statelessF3->mNumBones; unsigned char boneIdxs[120]; - if (boneMask != mBoneMask) { - mBoneMask = boneMask; - } - for (int ibone = 0; ibone < nBones; ibone++) { boneIdxs[ibone] = GetStatelessF3BoneIndex(dofIdxs[ibone]); } - if (!slerpReqd) { - for (int ibone = 0; ibone < nBones; ibone++) { - if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector3 value; - int index = dofIdxs[ibone]; - - UnquantizeStatelessF3(dofInfos[ibone], frameData, value); - sqt[index + 0] = value.x; - sqt[index + 1] = value.y; - sqt[index + 2] = value.z; - } - frameData += 3; - } - } else { + if (slerpReqd) { short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { @@ -224,6 +211,19 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo frameData += 3; nextFrameData += 3; } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector3 value; + int index = dofIdxs[ibone]; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, value); + sqt[index + 0] = value.x; + sqt[index + 1] = value.y; + sqt[index + 2] = value.z; + } + frameData += 3; + } } if (statelessF3->mNumConstBones != 0) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 5f9943f57..db8243343 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -194,31 +194,17 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + } + StatelessQ *statelessQ = reinterpret_cast(mpAnim); unsigned short *dataBuf = statelessQ->GetData(); unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; - if (boneMask != mBoneMask) { - mBoneMask = boneMask; - } - - if (!slerpReqd || floorKey >= statelessQ->mNumKeys - 1) { - for (int ibone = 0; ibone < nBones; ibone++) { - unsigned char boneIdx = boneIdxs[ibone]; - - if (boneMask->GetBone(boneIdx)) { - float *q = GetStatelessQOutput(sqt, boneIdx); - - q[0] = UncompressStatelessQValue(frameData[0]); - q[1] = UncompressStatelessQValue(frameData[1]); - q[2] = UncompressStatelessQValue(frameData[2]); - q[3] = UncompressStatelessQValue(frameData[3]); - } - frameData += 4; - } - } else { + if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { @@ -240,6 +226,20 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool frameData += 4; nextFrameData += 4; } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (boneMask->GetBone(boneIdx)) { + float *q = GetStatelessQOutput(sqt, boneIdx); + + q[0] = UncompressStatelessQValue(frameData[0]); + q[1] = UncompressStatelessQValue(frameData[1]); + q[2] = UncompressStatelessQValue(frameData[2]); + q[3] = UncompressStatelessQValue(frameData[3]); + } + frameData += 4; + } } if (statelessQ->mNumConstBones != 0) { From 4bc804308f0ed2acffbc6c4503a7f341440de831 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:21:18 +0100 Subject: [PATCH 066/372] 68.1%: bias stateless q masked output base Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index db8243343..5d0c248ae 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -199,6 +199,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool } StatelessQ *statelessQ = reinterpret_cast(mpAnim); + sqt += 4; unsigned short *dataBuf = statelessQ->GetData(); unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; @@ -213,7 +214,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool if (boneMask->GetBone(boneIdx)) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - float *q = GetStatelessQOutput(sqt, boneIdx); + float *q = &sqt[boneIdx * 12]; LoadStatelessQ(frameData, prevQ); LoadStatelessQ(nextFrameData, nextQ); @@ -231,7 +232,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - float *q = GetStatelessQOutput(sqt, boneIdx); + float *q = &sqt[boneIdx * 12]; q[0] = UncompressStatelessQValue(frameData[0]); q[1] = UncompressStatelessQValue(frameData[1]); @@ -250,7 +251,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned char boneIdx = constIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - float *q = GetStatelessQOutput(sqt, boneIdx); + float *q = &sqt[boneIdx * 12]; q[0] = UncompressStatelessQValue(constBuf[0]); q[1] = UncompressStatelessQValue(constBuf[1]); From b57b6e827e0446f608e95b3cb6ed9ec205bd0cad Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:23:59 +0100 Subject: [PATCH 067/372] 68.2%: bias qfast masked output base Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 0e476bb01..7a0b37e02 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -717,6 +717,8 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM } DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + float *quatBase = sqt + 4; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); @@ -728,7 +730,7 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + if (!boneMask->GetBone(boneIdxs[ibone])) { continue; } @@ -756,20 +758,23 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM float t = ComputeQFastBlendT(deltaQ, floorKey, ceilKey, currTime); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (!boneMask->GetBone(boneIdx)) { continue; } - FastQuatBlendF4(t, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&mNextQs[ibone]), - GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone])); + FastQuatBlendF4(t, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&mNextQs[ibone]), &quatBase[boneIdx * 12]); } } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (!boneMask->GetBone(boneIdx)) { continue; } - float *out = GetQFastOutputQuat(sqt, deltaQ->mBoneIdxs[ibone]); + float *out = &quatBase[boneIdx * 12]; out[0] = mPrevQs[ibone].x; out[1] = mPrevQs[ibone].y; @@ -779,12 +784,14 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM } for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { - if (!boneMask->GetBone(mConstBoneIdxs[ibone])) { + unsigned char boneIdx = mConstBoneIdxs[ibone]; + + if (!boneMask->GetBone(boneIdx)) { continue; } UMath::Vector4 q; - float *out = GetQFastOutputQuat(sqt, mConstBoneIdxs[ibone]); + float *out = &quatBase[boneIdx * 12]; DecodeQFastPhysical(mConstPhysical[ibone], q); out[0] = q.x; From 32613c422cb348615aa8db830a7220a0cf59bba3 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:26:01 +0100 Subject: [PATCH 068/372] 68.2%: hoist qfast masked bone indices Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 7a0b37e02..d0bd77823 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -317,12 +317,13 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs, const BoneMask *boneMask) { unsigned char *binData = reinterpret_cast(floorPhys); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + if (!boneMask->GetBone(boneIdxs[ibone])) { continue; } @@ -340,12 +341,13 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs, const BoneMask *boneMask) { unsigned char *binData = reinterpret_cast(floorPhys); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(deltaQ->mBoneIdxs[ibone])) { + if (!boneMask->GetBone(boneIdxs[ibone])) { continue; } @@ -367,13 +369,14 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi int ceilBinIdx = ceilKey >> (deltaQ->mBinLengthPower & 0x3F); unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (ceilBinIdx == floorBinIdx) { unsigned int numBones = deltaQ->mNumBones; unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; for (int ibone = 0; ibone < static_cast(numBones); ibone++) { - unsigned char boneIdx = deltaQ->mBoneIdxs[ibone]; + unsigned char boneIdx = boneIdxs[ibone]; if (!boneMask->GetBone(boneIdx)) { continue; } @@ -391,7 +394,6 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi nextQ[3] = minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + minRange[3] + prevQ[3]; - numBones = deltaQ->mNumBones; deltaData += 3; } } else { @@ -399,7 +401,7 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi unsigned short *physical = reinterpret_cast(binData); for (int ibone = 0; ibone < static_cast(numBones); ibone++) { - unsigned char boneIdx = deltaQ->mBoneIdxs[ibone]; + unsigned char boneIdx = boneIdxs[ibone]; if (!boneMask->GetBone(boneIdx)) { physical += 3; continue; @@ -432,8 +434,8 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } if (mBoneMask) { mBoneMask = nullptr; - mNextKey = -1; mPrevKey = -1; + mNextKey = -1; } DeltaQFast *deltaQ = reinterpret_cast(mpAnim); From 141eb3b697c5993d948785f7c81c39d67abc9771 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:30:09 +0100 Subject: [PATCH 069/372] 68.5%: flip qfast next-q branch ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index d0bd77823..7a5c86067 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -271,10 +271,25 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx return; } - int ceilBinIdx = ceilKey >> (deltaQ->mBinLengthPower & 0x3F); + int ceilBinIdx = ceilKey >> deltaQ->mBinLengthPower; unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; - if (ceilBinIdx == floorBinIdx) { + if (ceilBinIdx != floorBinIdx) { + unsigned int numBones = deltaQ->mNumBones; + unsigned short *physical = reinterpret_cast(binData); + + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + physical += 3; + } + } else { unsigned int numBones = deltaQ->mNumBones; unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; @@ -294,21 +309,6 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx minRange[3] + prevQ[3]; deltaData += 3; } - } else { - unsigned int numBones = deltaQ->mNumBones; - unsigned short *physical = reinterpret_cast(binData); - - for (int ibone = 0; ibone < static_cast(numBones); ibone++) { - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - - nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; - physical += 3; - } } mNextKey = ceilKey; @@ -367,11 +367,32 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi return; } - int ceilBinIdx = ceilKey >> (deltaQ->mBinLengthPower & 0x3F); + int ceilBinIdx = ceilKey >> deltaQ->mBinLengthPower; unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; unsigned char *boneIdxs = deltaQ->mBoneIdxs; - if (ceilBinIdx == floorBinIdx) { + if (ceilBinIdx != floorBinIdx) { + unsigned int numBones = deltaQ->mNumBones; + unsigned short *physical = reinterpret_cast(binData); + + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + if (!boneMask->GetBone(boneIdx)) { + physical += 3; + continue; + } + + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + physical += 3; + } + } else { unsigned int numBones = deltaQ->mNumBones; unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; @@ -396,27 +417,6 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi minRange[3] + prevQ[3]; deltaData += 3; } - } else { - unsigned int numBones = deltaQ->mNumBones; - unsigned short *physical = reinterpret_cast(binData); - - for (int ibone = 0; ibone < static_cast(numBones); ibone++) { - unsigned char boneIdx = boneIdxs[ibone]; - if (!boneMask->GetBone(boneIdx)) { - physical += 3; - continue; - } - - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - - nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; - physical += 3; - } } mNextKey = ceilKey; @@ -498,7 +498,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } binLengthPower = deltaQ->mBinLengthPower; - floorBinIdx = static_cast(floorKey) >> (binLengthPower & 0x3F); + floorBinIdx = static_cast(floorKey) >> binLengthPower; binLengthMask = 0x7FFFFFFFU >> ((0x1F - binLengthPower) & 0x3F); floorDeltaIdx = floorKey & binLengthMask; @@ -526,7 +526,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mNextKey = prevKey; } - if (prevDeltaIdx == -1 || floorBinIdx != (prevKey >> (binLengthPower & 0x3F)) || floorDeltaIdx == 0 || + if (prevDeltaIdx == -1 || floorBinIdx != (prevKey >> binLengthPower) || floorDeltaIdx == 0 || (floorKey < prevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); From b38282f02d58b278d944d33e6c96da4034dc78ee Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:32:15 +0100 Subject: [PATCH 070/372] 68.6%: tighten qfast cached next reuse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 7a5c86067..0387c9ab7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -499,7 +499,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) binLengthPower = deltaQ->mBinLengthPower; floorBinIdx = static_cast(floorKey) >> binLengthPower; - binLengthMask = 0x7FFFFFFFU >> ((0x1F - binLengthPower) & 0x3F); + binLengthMask = 0x7FFFFFFFU >> (0x1F - binLengthPower); floorDeltaIdx = floorKey & binLengthMask; if (mNextKey == floorKey) { @@ -514,13 +514,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) if (prevKey == static_cast(floorKey + 1)) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - - prevQ[0] = nextQ[0]; - prevQ[1] = nextQ[1]; - prevQ[2] = nextQ[2]; - prevQ[3] = nextQ[3]; + mPrevQs[ibone] = mNextQs[ibone]; } mNextKey = prevKey; From 76a69564f6216ca71164d887b88cee912849aae3 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:39:57 +0100 Subject: [PATCH 071/372] 68.6%: walk qfast masked delta pointers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 0387c9ab7..d2ee6c87b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -323,17 +323,16 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(boneIdxs[ibone])) { - continue; + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], deltaData, delta); + prevQs[ibone].x += delta.x; + prevQs[ibone].y += delta.y; + prevQs[ibone].z += delta.z; + prevQs[ibone].w += delta.w; } - - UMath::Vector4 delta; - - DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); - prevQs[ibone].x += delta.x; - prevQs[ibone].y += delta.y; - prevQs[ibone].z += delta.z; - prevQs[ibone].w += delta.w; + deltaData += 3; } } } @@ -347,17 +346,16 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - if (!boneMask->GetBone(boneIdxs[ibone])) { - continue; + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + + DecodeQFastDelta(mMinRangesf[ibone], deltaData, delta); + prevQs[ibone].x -= delta.x; + prevQs[ibone].y -= delta.y; + prevQs[ibone].z -= delta.z; + prevQs[ibone].w -= delta.w; } - - UMath::Vector4 delta; - - DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); - prevQs[ibone].x -= delta.x; - prevQs[ibone].y -= delta.y; - prevQs[ibone].z -= delta.z; - prevQs[ibone].w -= delta.w; + deltaData += 3; } } } From f71c9dea9bb51e635dbc3304437c0cf49a283027 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:40:54 +0100 Subject: [PATCH 072/372] 68.6%: simplify qfast masked updater branches Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index d2ee6c87b..e4c6a3947 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -375,19 +375,17 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi for (int ibone = 0; ibone < static_cast(numBones); ibone++) { unsigned char boneIdx = boneIdxs[ibone]; - if (!boneMask->GetBone(boneIdx)) { - physical += 3; - continue; + if (boneMask->GetBone(boneIdx)) { + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = + static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } - - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - - nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; physical += 3; } } else { @@ -396,23 +394,21 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi for (int ibone = 0; ibone < static_cast(numBones); ibone++) { unsigned char boneIdx = boneIdxs[ibone]; - if (!boneMask->GetBone(boneIdx)) { - continue; + if (boneMask->GetBone(boneIdx)) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + nextQ[0] = minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0] + prevQ[0]; + nextQ[1] = minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1] + prevQ[1]; + nextQ[2] = minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2] + prevQ[2]; + nextQ[3] = + minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + + minRange[3] + prevQ[3]; } - - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - nextQ[0] = minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0] + prevQ[0]; - nextQ[1] = minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1] + prevQ[1]; - nextQ[2] = minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2] + prevQ[2]; - nextQ[3] = - minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + - minRange[3] + prevQ[3]; deltaData += 3; } } From 947a3482fb37d1343b932071568b3de20244858f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Sun, 22 Mar 2026 23:45:57 +0100 Subject: [PATCH 073/372] 69.2%: flip singleq slerp branch ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index ec2f58148..e30c67586 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -343,20 +343,13 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas int ceilBinIdx = ceilKey >> binLenPower; DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(GetSingleQBin(deltaQ, ceilBinIdx)); - if (ceilBinIdx == floorBinIdx) { - DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); - + if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; UMath::Vector4 interpq; UMath::Vector4 outq; - DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); - ceilq.x += mPrevQs[ibone].x; - ceilq.y += mPrevQs[ibone].y; - ceilq.z += mPrevQs[ibone].z; - ceilq.w += mPrevQs[ibone].w; - + DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; @@ -367,12 +360,19 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; } } else { + DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; UMath::Vector4 interpq; UMath::Vector4 outq; - DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); + DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + ceilq.w += mPrevQs[ibone].w; + interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; @@ -554,21 +554,14 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo int ceilBinIdx = ceilKey >> binLenPower; DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(GetSingleQBin(deltaQ, ceilBinIdx)); - if (ceilBinIdx == floorBinIdx) { - DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); - + if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; UMath::Vector4 interpq; UMath::Vector4 outq; - DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); - ceilq.x += mPrevQs[ibone].x; - ceilq.y += mPrevQs[ibone].y; - ceilq.z += mPrevQs[ibone].z; - ceilq.w += mPrevQs[ibone].w; - + DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; @@ -580,13 +573,20 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo } } } else { + DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; UMath::Vector4 interpq; UMath::Vector4 outq; - DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); + DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + ceilq.w += mPrevQs[ibone].w; + interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; From 4d70206160682926f83d391628a95d3151c5b7ce Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:23:38 +0100 Subject: [PATCH 074/372] 69.5%: flip deltaq masked slerp branch ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index d2a7be0de..78a040ee0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -491,30 +491,30 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq int ceilBinIdx = ceilKey >> binLenPower; DeltaQPhysical *ceilPhys = GetPhysical(GetBin(deltaQ, ceilBinIdx)); - if (ceilBinIdx == floorBinIdx) { - DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); - + if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 delta; UMath::Vector4 ceilq; - DecodeDelta(mMinRanges[ibone], ceilDelta[ibone], delta); - ceilq.x = mPrevQs[ibone].x + delta.x; - ceilq.y = mPrevQs[ibone].y + delta.y; - ceilq.z = mPrevQs[ibone].z + delta.z; - RecoverW(ceilDelta[ibone].mW, ceilq); - + DecodePhysical(ceilPhys[ibone], ceilq); FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), GetOutputQuat(sqt, boneIdxs[ibone])); } } } else { + DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; UMath::Vector4 ceilq; - DecodePhysical(ceilPhys[ibone], ceilq); + DecodeDelta(mMinRanges[ibone], ceilDelta[ibone], delta); + ceilq.x = mPrevQs[ibone].x + delta.x; + ceilq.y = mPrevQs[ibone].y + delta.y; + ceilq.z = mPrevQs[ibone].z + delta.z; + RecoverW(ceilDelta[ibone].mW, ceilq); + FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), GetOutputQuat(sqt, boneIdxs[ibone])); } From 276d79fa2d65dfacbb2cbe6786b410b99e07d323 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:26:33 +0100 Subject: [PATCH 075/372] 69.6%: reorder singleq compose helper dispatch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index e30c67586..25aea2530 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -154,10 +154,10 @@ static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, cons static inline void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, const UMath::Vector4 &post, UMath::Vector4 &result) { - if (index == 1) { - SingleQQuatMultXxYxZ(pre, mid, post, result); - } else if (index == 0) { + if (index == 0) { SingleQQuatMultXxQ(mid, post, result); + } else if (index == 1) { + SingleQQuatMultXxYxZ(pre, mid, post, result); } else { SingleQQuatMultQxZ(pre, mid, result); } From a92800f4a6a11b647135a0f122df31360ad91a4d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:29:08 +0100 Subject: [PATCH 076/372] 69.6%: rewrite singleq blend arithmetic order Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 25aea2530..00acd7fdb 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -350,10 +350,10 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas UMath::Vector4 outq; DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); - interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; - interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; - interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; - interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); @@ -373,10 +373,10 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas ceilq.z += mPrevQs[ibone].z; ceilq.w += mPrevQs[ibone].w; - interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; - interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; - interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; - interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); @@ -562,10 +562,10 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo UMath::Vector4 outq; DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); - interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; - interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; - interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; - interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); @@ -587,10 +587,10 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo ceilq.z += mPrevQs[ibone].z; ceilq.w += mPrevQs[ibone].w; - interpq.x = mPrevQs[ibone].x + (ceilq.x - mPrevQs[ibone].x) * scale; - interpq.y = mPrevQs[ibone].y + (ceilq.y - mPrevQs[ibone].y) * scale; - interpq.z = mPrevQs[ibone].z + (ceilq.z - mPrevQs[ibone].z) * scale; - interpq.w = mPrevQs[ibone].w + (ceilq.w - mPrevQs[ibone].w) * scale; + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); From 8bbecfb0533fa23a98e67e072ef501bb38b8c42a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:30:14 +0100 Subject: [PATCH 077/372] 70.0%: compose singleq quats directly into output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 00acd7fdb..c40185d84 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -347,7 +347,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 outq; + UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; @@ -356,8 +356,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); - *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); } } else { DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); @@ -365,7 +364,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 outq; + UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); ceilq.x += mPrevQs[ibone].x; @@ -379,16 +378,14 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); - *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); } } } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 outq; + UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], outq); - *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], *outq); } } @@ -559,7 +556,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 outq; + UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; @@ -568,8 +565,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); - *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); } } } else { @@ -579,7 +575,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 outq; + UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); ceilq.x += mPrevQs[ibone].x; @@ -593,18 +589,16 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], outq); - *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); } } } } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 outq; + UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], outq); - *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])) = outq; + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], *outq); } } } From 9d53fe6393f1dd341d4f8793e684feed9f90d13d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:34:25 +0100 Subject: [PATCH 078/372] 70.1%: walk deltaq masked packed deltas Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 78a040ee0..0aba9f0b0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -422,11 +422,12 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; - DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + DecodeDelta(mMinRanges[ibone], *floorDelta, delta); mPrevQs[ibone].x += delta.x; mPrevQs[ibone].y += delta.y; mPrevQs[ibone].z += delta.z; } + floorDelta++; } } } else if (prevDeltaIdx > floorDeltaIdx) { @@ -437,11 +438,12 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; - DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + DecodeDelta(mMinRanges[ibone], *floorDelta, delta); mPrevQs[ibone].x -= delta.x; mPrevQs[ibone].y -= delta.y; mPrevQs[ibone].z -= delta.z; } + floorDelta++; } } } @@ -457,8 +459,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - RecoverW(floorDelta[ibone].mW, mPrevQs[ibone]); + RecoverW(floorDelta->mW, mPrevQs[ibone]); } + floorDelta++; } } mPrevKey = floorKey; @@ -509,15 +512,16 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq UMath::Vector4 delta; UMath::Vector4 ceilq; - DecodeDelta(mMinRanges[ibone], ceilDelta[ibone], delta); + DecodeDelta(mMinRanges[ibone], *ceilDelta, delta); ceilq.x = mPrevQs[ibone].x + delta.x; ceilq.y = mPrevQs[ibone].y + delta.y; ceilq.z = mPrevQs[ibone].z + delta.z; - RecoverW(ceilDelta[ibone].mW, ceilq); + RecoverW(ceilDelta->mW, ceilq); FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), GetOutputQuat(sqt, boneIdxs[ibone])); } + ceilDelta++; } } } else { From 06cf43cda8f00694c39616c186735bfd98bd8ff5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:35:46 +0100 Subject: [PATCH 079/372] 70.2%: walk deltaq packed deltas Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 0aba9f0b0..7e6f5b0d5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -235,10 +235,11 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 delta; - DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + DecodeDelta(mMinRanges[ibone], *floorDelta, delta); mPrevQs[ibone].x += delta.x; mPrevQs[ibone].y += delta.y; mPrevQs[ibone].z += delta.z; + floorDelta++; } } } else if (prevDeltaIdx > floorDeltaIdx) { @@ -248,10 +249,11 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 delta; - DecodeDelta(mMinRanges[ibone], floorDelta[ibone], delta); + DecodeDelta(mMinRanges[ibone], *floorDelta, delta); mPrevQs[ibone].x -= delta.x; mPrevQs[ibone].y -= delta.y; mPrevQs[ibone].z -= delta.z; + floorDelta++; } } } @@ -264,7 +266,8 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, floorDeltaIdx - 1); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - RecoverW(floorDelta[ibone].mW, mPrevQs[ibone]); + RecoverW(floorDelta->mW, mPrevQs[ibone]); + floorDelta++; } } mPrevKey = floorKey; @@ -312,14 +315,15 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { UMath::Vector4 delta; UMath::Vector4 ceilq; - DecodeDelta(mMinRanges[ibone], ceilDelta[ibone], delta); + DecodeDelta(mMinRanges[ibone], *ceilDelta, delta); ceilq.x = mPrevQs[ibone].x + delta.x; ceilq.y = mPrevQs[ibone].y + delta.y; ceilq.z = mPrevQs[ibone].z + delta.z; - RecoverW(ceilDelta[ibone].mW, ceilq); + RecoverW(ceilDelta->mW, ceilq); FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), GetOutputQuat(sqt, boneIdxs[ibone])); + ceilDelta++; } } } else { From 5f9e8c8cb84cf43ec79f367b66f0a536c64207e5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:37:49 +0100 Subject: [PATCH 080/372] 70.2%: reorder singleq prev-q buffer ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index c40185d84..8295f8065 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -199,8 +199,8 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas if (deltaQ->mNumBones != 0) { float eul[3]; - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; + mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); mPostMultQs = @@ -403,8 +403,8 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (deltaQ->mNumBones != 0) { float eul[3]; - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; + mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); mPostMultQs = From dc731b699b947a76779ec2a9d4143f7b29d06975 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:39:10 +0100 Subject: [PATCH 081/372] 70.2%: reorder singleq min-range decode stores Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 8295f8065..559e13b54 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -104,13 +104,13 @@ static inline void SingleQQuatMultQxZ(const UMath::Vector4 &a, const UMath::Vect } static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSingleQMinRangef &minRangef) { - minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit - kSingleQPi; - minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit - kSingleQPi; minRangef.mMin[0] = minRange.mMin[0] * kSingleQRangeScale16Bit - kSingleQFloatOne; minRangef.mMin[1] = minRange.mMin[1] * kSingleQRangeScale16Bit - kSingleQFloatOne; minRangef.mRange[0] = 2.0f * (minRange.mRange[0] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; minRangef.mRange[1] = 2.0f * (minRange.mRange[1] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; minRangef.mIndex = static_cast(minRange.mIndex); + minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit - kSingleQPi; + minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit - kSingleQPi; } static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { From 1baf855162f441e27306be5282f1f949d31acb5f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:40:15 +0100 Subject: [PATCH 082/372] 70.3%: rewrite singleq angle decode as additive form Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 559e13b54..632a6636d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -109,8 +109,8 @@ static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, D minRangef.mRange[0] = 2.0f * (minRange.mRange[0] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; minRangef.mRange[1] = 2.0f * (minRange.mRange[1] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; minRangef.mIndex = static_cast(minRange.mIndex); - minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit - kSingleQPi; - minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit - kSingleQPi; + minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit + -kSingleQPi; + minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit + -kSingleQPi; } static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { From 59d714f890c53bd31db00aee02281b49656cef94 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:41:53 +0100 Subject: [PATCH 083/372] 70.4%: initialize singleq pre/post identities per case Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 632a6636d..0da29aa1d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -211,16 +211,11 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas DecodeSingleQMinRange(mMinRanges[ibone], minRangef); - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - if (minRangef.mIndex == 0) { + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; eul[0] = kSingleQFloatZero; eul[1] = minRangef.mConst0; eul[2] = minRangef.mConst1; @@ -236,6 +231,10 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas eul[2] = minRangef.mConst1; SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); } else { + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; eul[0] = minRangef.mConst0; eul[1] = minRangef.mConst1; eul[2] = kSingleQFloatZero; @@ -415,16 +414,11 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo DecodeSingleQMinRange(mMinRanges[ibone], minRangef); - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - if (minRangef.mIndex == 0) { + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; eul[0] = kSingleQFloatZero; eul[1] = minRangef.mConst0; eul[2] = minRangef.mConst1; @@ -440,6 +434,10 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo eul[2] = minRangef.mConst1; SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); } else { + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; eul[0] = minRangef.mConst0; eul[1] = minRangef.mConst1; eul[2] = kSingleQFloatZero; @@ -628,16 +626,11 @@ void FnDeltaSingleQ::InitBuffersAsRequired() { DecodeSingleQMinRange(mMinRanges[ibone], minRangef); - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - if (minRangef.mIndex == 0) { + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; eul[0] = kSingleQFloatZero; eul[1] = minRangef.mConst0; eul[2] = minRangef.mConst1; @@ -653,6 +646,10 @@ void FnDeltaSingleQ::InitBuffersAsRequired() { eul[2] = minRangef.mConst1; SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); } else { + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; eul[0] = minRangef.mConst0; eul[1] = minRangef.mConst1; eul[2] = kSingleQFloatZero; From b81bb36eba12cdc3bfcdd7817415ccdf49f1cca7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 00:45:51 +0100 Subject: [PATCH 084/372] 70.4%: inline masked deltaq reverse gate Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 7e6f5b0d5..067e3d0f8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -405,9 +405,8 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq unsigned char *binData = GetBin(deltaQ, floorBinIdx); DeltaQPhysical *floorPhys = GetPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; - bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); - - if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || + (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { DecodePhysical(floorPhys[ibone], mPrevQs[ibone]); From e296499afaedb740378875e4c20d894cde7263a6 Mon Sep 17 00:00:00 2001 From: JohnDeved Date: Mon, 23 Mar 2026 05:16:01 +0100 Subject: [PATCH 085/372] Squash JohnDeved-zTrack into dev Co-authored-by: dbalatoni13 <40299962+dbalatoni13@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/SourceLists/zTrack.cpp | 25 + src/Speed/Indep/Src/Camera/CameraMover.hpp | 4 + src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp | 126 +- src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp | 4 + src/Speed/Indep/Src/Misc/ResourceLoader.hpp | 9 + src/Speed/Indep/Src/World/Clans.cpp | 112 + src/Speed/Indep/Src/World/Clans.hpp | 49 + src/Speed/Indep/Src/World/EventManager.cpp | 434 +++ src/Speed/Indep/Src/World/ParameterMaps.cpp | 4 + src/Speed/Indep/Src/World/ParameterMaps.hpp | 63 + src/Speed/Indep/Src/World/Rain.hpp | 94 + src/Speed/Indep/Src/World/Scenery.cpp | 1331 +++++++++ src/Speed/Indep/Src/World/Scenery.hpp | 181 +- src/Speed/Indep/Src/World/ScreenEffects.cpp | 497 ++++ src/Speed/Indep/Src/World/ScreenEffects.hpp | 15 + src/Speed/Indep/Src/World/Skids.cpp | 348 +++ src/Speed/Indep/Src/World/Skids.hpp | 92 + src/Speed/Indep/Src/World/Track.cpp | 110 + src/Speed/Indep/Src/World/Track.hpp | 8 + src/Speed/Indep/Src/World/TrackInfo.cpp | 105 + src/Speed/Indep/Src/World/TrackInfo.hpp | 14 + src/Speed/Indep/Src/World/TrackPath.cpp | 267 ++ src/Speed/Indep/Src/World/TrackPath.hpp | 69 +- .../Indep/Src/World/TrackPositionMarker.cpp | 93 + .../Indep/Src/World/TrackPositionMarker.hpp | 6 + src/Speed/Indep/Src/World/TrackStreamer.cpp | 2488 +++++++++++++++++ src/Speed/Indep/Src/World/TrackStreamer.hpp | 104 +- src/Speed/Indep/Src/World/VisibleSection.cpp | 922 ++++++ src/Speed/Indep/Src/World/VisibleSection.hpp | 207 +- src/Speed/Indep/Src/World/WWorldPos.h | 4 +- src/Speed/Indep/Src/World/WeatherMan.cpp | 275 ++ src/Speed/Indep/Src/World/WeatherMan.hpp | 6 + src/Speed/Indep/bWare/Inc/Espresso.hpp | 14 + src/Speed/Indep/bWare/Inc/bList.hpp | 10 +- src/Speed/Indep/bWare/Inc/bMath.hpp | 66 +- src/Speed/Indep/bWare/Inc/bMemory.hpp | 14 - src/Speed/Indep/bWare/Inc/bWare.hpp | 22 + 37 files changed, 8036 insertions(+), 156 deletions(-) create mode 100644 src/Speed/Indep/bWare/Inc/Espresso.hpp diff --git a/src/Speed/Indep/SourceLists/zTrack.cpp b/src/Speed/Indep/SourceLists/zTrack.cpp index e69de29bb..6bc74464d 100644 --- a/src/Speed/Indep/SourceLists/zTrack.cpp +++ b/src/Speed/Indep/SourceLists/zTrack.cpp @@ -0,0 +1,25 @@ +#include "Speed/Indep/Src/World/Skids.cpp" + +#include "Speed/Indep/Src/World/Clans.cpp" + +#include "Speed/Indep/Src/World/Track.cpp" + +#include "Speed/Indep/Src/World/TrackPositionMarker.cpp" + +#include "Speed/Indep/Src/World/TrackInfo.cpp" + +#include "Speed/Indep/Src/World/TrackPath.cpp" + +#include "Speed/Indep/Src/World/TrackStreamer.cpp" + +#include "Speed/Indep/Src/World/Scenery.cpp" + +#include "Speed/Indep/Src/World/VisibleSection.cpp" + +#include "Speed/Indep/Src/World/ScreenEffects.cpp" + +#include "Speed/Indep/Src/World/WeatherMan.cpp" + +#include "Speed/Indep/Src/World/ParameterMaps.cpp" + +#include "Speed/Indep/Src/World/EventManager.cpp" diff --git a/src/Speed/Indep/Src/Camera/CameraMover.hpp b/src/Speed/Indep/Src/Camera/CameraMover.hpp index e2ef17946..673d751be 100644 --- a/src/Speed/Indep/Src/Camera/CameraMover.hpp +++ b/src/Speed/Indep/Src/Camera/CameraMover.hpp @@ -40,6 +40,10 @@ enum CameraMoverTypes { // total size: 0x124 class CameraAnchor { public: + bVector3 *GetGeometryPosition() { + return &mGeomPos; + } + unsigned int GetWorldID() const { return mWorldID; } diff --git a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp index 6d8c4dd38..20809cac3 100644 --- a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp +++ b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp @@ -184,6 +184,10 @@ struct eView : public eViewPlatInterface { this->Active = state; } + int IsActive() const { + return Active; + } + CameraMover *GetCameraMover() { if (!this->CameraMoverList.IsEmpty()) { return this->CameraMoverList.GetHead(); @@ -229,6 +233,13 @@ enum ScreenEffectControl { SEC_FRAME = 0, }; +enum ScreenEffectPalette { + EFX_CAMERA_FLASH = 0, + EFX_TUNNEL = 1, + EFX_UNIQUE = 2, + EFX_NUMBER = 3, +}; + struct ScreenEffectInf { // total size: 0xC ScreenEffectControl Controller; // offset 0x0, size 0x4 @@ -247,6 +258,14 @@ struct ScreenEffectDef { struct ScreenEffectDB *); // offset 0x4C, size 0x4 }; +struct ScreenEffectPaletteDef { + // total size: 0x10C + int NumEffects; // offset 0x0, size 0x4 + ScreenEffectType SE_type[3]; // offset 0x4, size 0xC + ScreenEffectDef SE_Def[3]; // offset 0x10, size 0xF0 + ScreenEffectControl SE_Controller[3]; // offset 0x100, size 0xC +}; + struct ScreenEffectDB { // total size: 0x1E8 eView *MyView; // offset 0x0, size 0x4 @@ -256,6 +275,18 @@ struct ScreenEffectDB { float SE_time; // offset 0x1E4, size 0x4 ScreenEffectDB(); + void Update(float deltatime); + void AddScreenEffect(ScreenEffectType type, float intensity, float r, float g, float b); + void AddScreenEffect(ScreenEffectType type, ScreenEffectDef *info, unsigned int lock, ScreenEffectControl controller); + void AddPaletteEffect(ScreenEffectPalette palette); + void AddPaletteEffect(ScreenEffectPaletteDef *palette); + float GetIntensity(ScreenEffectType type); + float GetDATA(ScreenEffectType type, int index); + void SetDATA(ScreenEffectType type, float data, int index); + + void SetController(ScreenEffectType type, ScreenEffectControl SEC) { + SE_inf[type].Controller = SEC; + } void SetMyView(eView *view) { MyView = view; @@ -271,6 +302,8 @@ struct ePoly { unsigned char flags; // offset 0x90, size 0x1 unsigned char Flailer; // offset 0x91, size 0x1 + ePoly(); + void *operator new(size_t size) {} void operator delete(void *ptr) {} @@ -312,90 +345,6 @@ enum RainWindType { POINT_WIND = 0, }; -// total size: 0x47C -struct Rain { - OnScreenRain OSrain; // offset 0x0, size 0x4 - int NoRain; // offset 0x4, size 0x4 - int NoRainAhead; // offset 0x8, size 0x4 - int inTunnel; // offset 0xC, size 0x4 - int inOverpass; // offset 0x10, size 0x4 - void *the_zone; // offset 0x14, size 0x4 - bVector3 outvex; // offset 0x18, size 0x10 - bVector2 twoDpos; // offset 0x28, size 0x8 - bVector3 CamVelLOCAL; // offset 0x30, size 0x10 - CurtainStatus IsValidRainCurtainPos; // offset 0x40, size 0x4 - int renderCount; // offset 0x44, size 0x4 - private: - eView *MyView; // offset 0x48, size 0x4 - float intensity; // offset 0x4C, size 0x4 - float CloudIntensity; // offset 0x50, size 0x4 - uint32 DesiredActive; // offset 0x54, size 0x4 - uint32 NewSwapBuffer; // offset 0x58, size 0x4 - uint32 OldSwapBuffer; // offset 0x5C, size 0x4 - int32 NumRainPoints; // offset 0x60, size 0x4 - unsigned int NumOfType[2]; // offset 0x64, size 0x8 - unsigned int DesiredNumOfType[2]; // offset 0x6C, size 0x8 - float Percentages[2]; // offset 0x74, size 0x8 - float precipWindEffect[2][2]; // offset 0x7C, size 0x10 - TextureInfo *texture_info[2]; // offset 0x8C, size 0x8 - bVector3 OldCarDirection; // offset 0x94, size 0x10 - bVector3 precipRadius[2]; // offset 0xA4, size 0x20 - bVector3 precipSpeedRange[2]; // offset 0xC4, size 0x20 - float precipZconstant[2]; // offset 0xE4, size 0x8 - RainWindType windType[2]; // offset 0xEC, size 0x8 - float CameraSpeed; // offset 0xF4, size 0x4 - bVector3 windSpeed; // offset 0xF8, size 0x10 - bVector3 DesiredwindSpeed; // offset 0x108, size 0x10 - float DesiredWindTime; // offset 0x118, size 0x4 - float windTime; // offset 0x11C, size 0x4 - uint32 fogR; // offset 0x120, size 0x4 - uint32 fogG; // offset 0x124, size 0x4 - uint32 fogB; // offset 0x128, size 0x4 - bVector2 aabbMax; // offset 0x12C, size 0x8 - bVector2 aabbMin; // offset 0x134, size 0x8 - ePoly PRECIPpoly[2]; // offset 0x13C, size 0x128 - bMatrix4 local2world; // offset 0x264, size 0x40 - bMatrix4 world2localrot; // offset 0x2A4, size 0x40 - float LenModifier; // offset 0x2E4, size 0x4 - float DesiredIntensity; // offset 0x2E8, size 0x4 - float DesiredCloudyness; // offset 0x2EC, size 0x4 - float DesiredRoadDampness; // offset 0x2F0, size 0x4 - float RoadDampness; // offset 0x2F4, size 0x4 - float percentPrecip[2]; // offset 0x2F8, size 0x8 - bVector3 PrevailingWindSpeed; // offset 0x300, size 0x10 - float WeatherTime; // offset 0x310, size 0x4 - float DesiredWeatherTime; // offset 0x314, size 0x4 - bVector3 Velocities[10][2]; // offset 0x318, size 0x140 - bVector2 ent0; // offset 0x458, size 0x8 - bVector2 ent1; // offset 0x460, size 0x8 - bVector2 ext0; // offset 0x468, size 0x8 - bVector2 ext1; // offset 0x470, size 0x8 - uint8 entFLAG; // offset 0x478, size 0x1 - uint8 extFLAG; // offset 0x479, size 0x1 - - public: - Rain(eView *view, RainType StartType); - void Init(RainType type, float percent); - - float GetRainIntensity() {} - - float GetCloudIntensity() { - return this->CloudIntensity; - } - - float GetRoadDampness() {} - - void GetPrecipFogColour(unsigned int *r, unsigned int *g, unsigned int *b) {} - - void SetPrecipFogColour(unsigned int r, unsigned int g, unsigned int b) {} - - float GetAmount(RainType type) {} - - void SetRoadDampness(float damp) {} - - bVector3 *GetWind() {} -}; - struct FacePixelation { // total size: 0xC struct eView *MyView; // offset 0x0, size 0x4 @@ -516,6 +465,7 @@ int eSmoothNormals(eSolid **solid_table, int num_solids); extern eLoadedSolidStats LoadedSolidStats; extern unsigned int eFrameCounter; +extern int WaitUntilRenderingDoneDisabled; inline unsigned int eGetFrameCounter() { return eFrameCounter; @@ -525,4 +475,12 @@ inline int eLoadStreamingSolidPack(const char *filename) { return eLoadStreamingSolidPack(filename, nullptr, nullptr, 0); } +inline void DisableWaitUntilRenderingDone() { + WaitUntilRenderingDoneDisabled = 1; +} + +inline void EnableWaitUntilRenderingDone() { + WaitUntilRenderingDoneDisabled = 0; +} + #endif diff --git a/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp b/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp index 65a4c20dc..144eb246e 100644 --- a/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp +++ b/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp @@ -195,6 +195,9 @@ struct ePositionMarker { void EndianSwap() {} }; +struct eModel; +struct eLightContext; + class eViewPlatInfo; // total size: 0x4 @@ -212,6 +215,7 @@ class eViewPlatInterface { static eViewPlatInfo *GimmeMyViewPlatInfo(int view_id); eVisibleState GetVisibleStateGB(const bVector3 *aabb_min, const bVector3 *aabb_max, bMatrix4 *local_world); eVisibleState GetVisibleStateSB(const bVector3 *aabb_min, const bVector3 *aabb_max, bMatrix4 *local_world); + void Render(eModel *model, bMatrix4 *local_to_world, eLightContext *light_context, unsigned int flags, bMatrix4 *blending_matricies); }; struct eLoadedSolidStats { diff --git a/src/Speed/Indep/Src/Misc/ResourceLoader.hpp b/src/Speed/Indep/Src/Misc/ResourceLoader.hpp index 5b5ae2cd3..07c3c9b73 100644 --- a/src/Speed/Indep/Src/Misc/ResourceLoader.hpp +++ b/src/Speed/Indep/Src/Misc/ResourceLoader.hpp @@ -187,6 +187,11 @@ void EndianSwapChunkHeader(bChunk *chunk); void EndianSwapChunkHeadersRecursive(bChunk *chunks, int sizeof_chunks); void EndianSwapChunkHeadersRecursive(bChunk *first_chunk, bChunk *last_chunk); +int ServiceResourceLoading(); +ResourceFile *CreateResourceFile(const char *filename, ResourceFileType type, int flags, int flag_offset, int file_size); +void UnloadResourceFile(ResourceFile *resource_file); +void SetDelayedResourceCallback(void (*callback)(void *), void *param); + extern int ChunkMovementOffset; // size: 0x4 inline ResourceFile *LoadResourceFile(const char *filename, ResourceFileType type, int flags) { @@ -207,4 +212,8 @@ inline int GetChunkMovementOffset() { return ChunkMovementOffset; } +inline void SetDelayedResourceCallback(void (*callback)(int), int param) { + SetDelayedResourceCallback(reinterpret_cast(callback), reinterpret_cast(param)); +} + #endif diff --git a/src/Speed/Indep/Src/World/Clans.cpp b/src/Speed/Indep/Src/World/Clans.cpp index e69de29bb..49ead4c81 100644 --- a/src/Speed/Indep/Src/World/Clans.cpp +++ b/src/Speed/Indep/Src/World/Clans.cpp @@ -0,0 +1,112 @@ +#include "Clans.hpp" + +#include "Skids.hpp" +#include "Speed/Indep/bWare/Inc/bVector.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void bInitializeBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *point); + +extern int WorldTime; + +SlotPool *ClanSlotPool = 0; +bTList ClanList; + +inline void *Clan::operator new(size_t) { + return bOMalloc(ClanSlotPool); +} + +inline void Clan::operator delete(void *ptr) { + bFree(ClanSlotPool, ptr); +} + +void InitClans() { + if (!ClanSlotPool) { + ClanSlotPool = bNewSlotPool(0x48, 0x28, "ClanSlotPool", 0); + } +} + +void FlushClans() { + while (!ClanList.IsEmpty()) { + Clan *clan = ClanList.GetHead(); + ClanList.Remove(clan); + delete clan; + } +} + +void CloseClans() { + if (ClanSlotPool) { + FlushClans(); + bDeleteSlotPool(ClanSlotPool); + ClanSlotPool = 0; + } +} + +Clan *GetClan(bVector3 *position) { + int cx = (static_cast(position->x * 65536.0f) >> 22) & 0xffff; + int cy = (static_cast(position->y * 65536.0f) >> 22) * 0x10000; + unsigned int hash = static_cast(cx + cy); + Clan *clan = ClanList.GetHead(); + + if (clan != ClanList.EndOfList() && clan->GetHash() != hash) { + do { + clan = clan->GetNext(); + } while (clan != ClanList.EndOfList() && clan->GetHash() != hash); + } + + if (clan != ClanList.EndOfList()) { + clan->SetLastUpdateTime(WorldTime); + ClanList.Remove(clan); + ClanList.AddHead(clan); + return clan; + } + + if (bIsSlotPoolFull(ClanSlotPool)) { + clan = ClanList.GetTail(); + ClanList.Remove(clan); + delete clan; + } + + clan = new Clan(position, hash); + ClanList.AddHead(clan); + return clan; +} + +void RenderClans(eView *view) { + ProfileNode profile_node("TODO", 0); + Clan *clan = ClanList.GetHead(); + while (clan != ClanList.EndOfList()) { + Clan *next_clan = clan->GetNext(); + int pixel_size = view->GetPixelSize(clan->GetBBoxMin(), clan->GetBBoxMax()); + + if (pixel_size > 10) { + eVisibleState visibility = view->GetVisibleState(clan->GetBBoxMin(), clan->GetBBoxMax(), 0); + if (visibility != 0) { + if (WorldTime - clan->GetLastUpdateTime() > 300) { + clan->SetLastUpdateTime(WorldTime); + ClanList.Remove(clan); + ClanList.AddHead(clan); + } + RenderSkids(view, clan); + } + } + clan = next_clan; + } +} + +Clan::Clan(bVector3 *position, unsigned int hash) + : Hash(hash) +{ + Position = *position; + bInitializeBoundingBox(&BBoxMin, &BBoxMax, position); +} + +Clan::~Clan() { + while (SkidSetList.GetHead() != SkidSetList.EndOfList()) { + bPNode *node = SkidSetList.GetHead(); + DeleteThisSkid(reinterpret_cast(node->GetpObject())); + } + + while (SkidSetList.GetHead() != SkidSetList.EndOfList()) { + SkidSetList.RemoveHead(); + } +} diff --git a/src/Speed/Indep/Src/World/Clans.hpp b/src/Speed/Indep/Src/World/Clans.hpp index 37eaefff4..47e99c4f3 100644 --- a/src/Speed/Indep/Src/World/Clans.hpp +++ b/src/Speed/Indep/Src/World/Clans.hpp @@ -5,7 +5,56 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +struct SkidSet; +struct eView; + +void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *bbox2_min, const bVector3 *bbox2_max); + +class Clan : public bTNode { + public: + // total size: 0x48 + bPList SkidSetList; // offset 0x8, size 0x8 + + private: + unsigned int Hash; // offset 0x10, size 0x4 + int LastUpdateTime; // offset 0x14, size 0x4 + bVector3 Position; // offset 0x18, size 0x10 + bVector3 BBoxMin; // offset 0x28, size 0x10 + bVector3 BBoxMax; // offset 0x38, size 0x10 + + public: + void *operator new(size_t size); + void operator delete(void *ptr); + + Clan(bVector3 *position, unsigned int hash); + ~Clan(); + bVector3 *GetBBoxMin() { + return &BBoxMin; + } + bVector3 *GetBBoxMax() { + return &BBoxMax; + } + int GetLastUpdateTime() { + return LastUpdateTime; + } + unsigned int GetHash() { + return Hash; + } + void SetLastUpdateTime(int time) { + LastUpdateTime = time; + } + void ExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max) { + bExpandBoundingBox(&BBoxMin, &BBoxMax, bbox_min, bbox_max); + } +}; + void InitClans(); +void FlushClans(); void CloseClans(); +Clan *GetClan(bVector3 *position); +void RenderClans(eView *view); #endif diff --git a/src/Speed/Indep/Src/World/EventManager.cpp b/src/Speed/Indep/Src/World/EventManager.cpp index e69de29bb..865252fd6 100644 --- a/src/Speed/Indep/Src/World/EventManager.cpp +++ b/src/Speed/Indep/Src/World/EventManager.cpp @@ -0,0 +1,434 @@ +#include "Speed/Indep/Src/World/EventManager.hpp" + +#include "Speed/Indep/Src/Main/Event.h" +#include "Speed/Indep/Src/World/VisibleSection.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bDebug.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" + +struct vAABB { + float PositionX; + float PositionY; + float PositionZ; + short ParentIndex; + short NumChildren; + float ExtentX; + float ExtentY; + float ExtentZ; + short ChildrenIndicies[10]; +}; + +struct vAABBTree { + vAABB *NodeArray; + short NumLeafNodes; + short NumParentNodes; + short TotalNodes; + short Depth; + int pad1; + + vAABB *QueryLeaf(float x, float y, float z); +}; + +struct EventTrigger { + unsigned int NameHash; + unsigned int EventID; + unsigned int Parameter; + int TrackDirectionMask; + float PositionX; + float PositionY; + float PositionZ; + float Radius; + + unsigned int GetEventID() { + return EventID; + } + + float GetRadius() { + return Radius; + } +}; + +struct EventTriggerPack : public bTNode { + int Version; + int ScenerySectionNumber; + int NumEventTriggers; + int EndianSwapped; + vAABBTree *EventTree; + EventTrigger *EventTriggerArray; +}; + +extern SlotPool *EventSlotPool; + +enum EVENT_ID { + TRIGGER_EVENT_CAR_ON_FERN = 65539, + TRIGGER_EVENT_VIEW_DRIVING_LINE = 65541, + TRIGGER_EVENT_ACTIVATE_TRAIN = 65542, + TRIGGER_EVENT_SOUND = 65543, + TRIGGER_EVENT_GUIDE_ARROW = 65544, + TRIGGER_EVENT_ACTIVATE_PLANE = 65545, + TRIGGER_EVENT_INITIATE_PURSUIT = 131072, + TRIGGER_EVENT_CALL_FOR_BACKUP = 131073, + TRIGGER_EVENT_CALL_FOR_ROADBLOCK = 131074, + TRIGGER_EVENT_STRATEGY_INITIATE = 131075, + TRIGGER_EVENT_COLLISION = 131076, + TRIGGER_EVENT_ANNOUNCE_ARREST = 131077, + TRIGGER_EVENT_STRATEGY_OUTCOME = 131078, + TRIGGER_EVENT_ROADBLOCK_UPDATE = 131079, + TRIGGER_EVENT_CANCEL_PURSUIT = 131080, + TRIGGER_EVENT_START_SIREN = 262144, + TRIGGER_EVENT_STOP_SIREN = 262145 +}; + +struct emEvent : public bTNode { + static void *operator new(size_t size) { + return bOMalloc(EventSlotPool); + } + + static void operator delete(void *ptr) { + bFree(EventSlotPool, ptr); + } + + emEvent() { + } + + int ReferenceCount; + unsigned int ID; + void *pEventTrigger; + Car *CarPtr; + int Parameter0; + int Parameter1; + int Parameter2; +}; + +typedef void (*EVENT_HANDLER_FUNC)(emEvent *); + +struct emEventHandler : public bTNode { + EVENT_HANDLER_FUNC HandlerFunction; + unsigned int StreamMask; + int ReferenceCount; + float TotalTime; +}; + +void bEndianSwap32(void *value); +void SwapEndian(vAABBTree *tree); +emEvent *emAddEvent(EVENT_ID event_id); + +static inline bNode *GetEventTriggerPackNode_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack); +} + +static inline int *GetEventTriggerPackWords_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack); +} + +static inline int GetEventTriggerPackType_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[2]; +} + +static inline int GetEventTriggerPackSectionNumber_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[3]; +} + +static inline int GetEventTriggerPackNumEvents_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[4]; +} + +static inline int GetEventTriggerPackEndianSwapped_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[5]; +} + +static inline void SetEventTriggerPackEndianSwapped_EventManager(void *event_trigger_pack, int swapped) { + GetEventTriggerPackWords_EventManager(event_trigger_pack)[5] = swapped; +} + +static inline void *GetEventTriggerPackTree_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack)[6]; +} + +static inline void SetEventTriggerPackTree_EventManager(void *event_trigger_pack, void *tree) { + reinterpret_cast(event_trigger_pack)[6] = tree; +} + +static inline void *GetEventTriggerPackData_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack)[7]; +} + +static inline void SetEventTriggerPackData_EventManager(void *event_trigger_pack, void *data) { + reinterpret_cast(event_trigger_pack)[7] = data; +} + +static inline VisibleSectionUserInfo **GetUserInfoTable_EventManager() { + return reinterpret_cast(reinterpret_cast(&TheVisibleSectionManager) + 0x60); +} + +emEvent *TriggerEventArray[41]; +SlotPool *EventSlotPool = 0; +SlotPool *EventHandlerSlotPool = 0; +int EventManagerStats[5]; +bTList EmptyEventTriggerPackList; +bTList EventTriggerPackList; +bTList EventHandlerList; +bTList MasterEventQueue; +bTList *CurrentEventQueue = &MasterEventQueue; +emEvent *CurrentlyHandlingEvent = 0; + +void emEventManagerInit() { + EventSlotPool = bNewSlotPool(0x24, 0x3C, "EventSlotPool", 0); + EventHandlerSlotPool = bNewSlotPool(0x18, 0x14, "EventHandlerSlotPool", 0); +} + +int emAddHandler(EVENT_HANDLER_FUNC function, unsigned int stream_mask) { + if (function && stream_mask) { + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + if (handler->HandlerFunction == function) { + handler->ReferenceCount += 1; + return 1; + } + } + + emEventHandler *handler = reinterpret_cast(bOMalloc(EventHandlerSlotPool)); + if (!handler) { + return 0; + } + + handler->HandlerFunction = function; + handler->StreamMask = stream_mask; + handler->ReferenceCount = 1; + EventHandlerList.AddTail(handler); + EventManagerStats[1] += 1; + if (EventManagerStats[1] > EventManagerStats[4]) { + EventManagerStats[4] = EventManagerStats[1]; + } + return 1; + } + + return 0; +} + +void emRemoveHandler(EVENT_HANDLER_FUNC function) { + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + if (handler->HandlerFunction == function) { + int ref_count = handler->ReferenceCount - 1; + handler->ReferenceCount = ref_count; + if (ref_count == 0) { + if (handler->Remove()) { + bFree(EventHandlerSlotPool, handler); + } + EventManagerStats[1] -= 1; + } + return; + } + } +} + +emEvent *emAddEvent(EVENT_ID event_id) { + emEvent *event = new emEvent; + if (!event) { + return 0; + } + + bMemSet(event, 0, sizeof(emEvent)); + event->ReferenceCount = 0; + event->ID = event_id; + CurrentEventQueue->AddTail(event); + EventManagerStats[0] += 1; + return event; +} + +int LoaderEventManager(bChunk *bchunk) { + if (bchunk->GetID() != 0x80036000) { + return false; + } + + EventTriggerPack *trigger_pack = 0; + bChunk *chunk = &bchunk[1]; + bChunk *last_chunk = reinterpret_cast(reinterpret_cast(bchunk) + bchunk->Size) + 1; + for (; chunk != last_chunk; chunk = chunk->GetNext()) { + switch (chunk->GetID()) { + case 0x36001: { + trigger_pack = reinterpret_cast(chunk->GetAlignedData(0x10)); + if (trigger_pack->EndianSwapped == 0) { + bPlatEndianSwap(&trigger_pack->ScenerySectionNumber); + bPlatEndianSwap(&trigger_pack->Version); + bPlatEndianSwap(&trigger_pack->NumEventTriggers); + } + + if (trigger_pack->Version != 2) { + return true; + } + + VisibleSectionUserInfo *user_info = + TheVisibleSectionManager.AllocateUserInfo(trigger_pack->ScenerySectionNumber); + user_info->pEventTriggerPack = trigger_pack; + break; + } + + case 0x36002: + if (trigger_pack) { + trigger_pack->EventTree = reinterpret_cast(chunk->GetAlignedData(0x10)); + trigger_pack->EventTree->NodeArray = + reinterpret_cast(reinterpret_cast(trigger_pack->EventTree) + 4); + if (trigger_pack->EndianSwapped == 0) { + SwapEndian(trigger_pack->EventTree); + } + } + break; + + case 0x36003: + if (trigger_pack) { + trigger_pack->EventTriggerArray = reinterpret_cast(chunk->GetAlignedData(0x10)); + if (trigger_pack->EndianSwapped == 0) { + int num_triggers = static_cast(chunk->GetAlignedSize(0x10)) >> 5; + for (int n = 0; n < num_triggers; n++) { + EventTrigger *event_trigger = &trigger_pack->EventTriggerArray[n]; + bPlatEndianSwap(&event_trigger->NameHash); + bPlatEndianSwap(&event_trigger->EventID); + bPlatEndianSwap(&event_trigger->Parameter); + bPlatEndianSwap(&event_trigger->TrackDirectionMask); + bPlatEndianSwap(&event_trigger->PositionX); + bPlatEndianSwap(&event_trigger->PositionY); + bPlatEndianSwap(&event_trigger->PositionZ); + bPlatEndianSwap(&event_trigger->Radius); + } + } + } + break; + } + } + + trigger_pack->EndianSwapped = 1; + if (trigger_pack) { + if (trigger_pack->NumEventTriggers != 0 && trigger_pack->EventTree && trigger_pack->EventTriggerArray) { + EventTriggerPackList.AddTail(trigger_pack); + } else { + EmptyEventTriggerPackList.AddTail(trigger_pack); + } + } + + return true; +} + +int UnloaderEventManager(bChunk *bchunk) { + if (bchunk->GetID() != 0x80036000) { + return false; + } + + bChunk *chunk = bchunk->GetFirstChunk(); + bChunk *last_chunk = bchunk->GetLastChunk(); + if (chunk != last_chunk) { + do { + if (chunk->GetID() == 0x36001) { + EventTriggerPack *trigger_pack = reinterpret_cast(bchunk->GetAlignedData(0x10)); + if (trigger_pack->Version == 2) { + trigger_pack->Remove(); + } + + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(trigger_pack->ScenerySectionNumber); + user_info->pEventTriggerPack = 0; + TheVisibleSectionManager.UnallocateUserInfo(trigger_pack->ScenerySectionNumber); + break; + } + + chunk = reinterpret_cast(reinterpret_cast(chunk) + chunk->Size) + 1; + } while (chunk != last_chunk); + } + + return true; +} + +emEvent **emTriggerEventsInSection(bVector3 *position, int section_number) { + emEvent **current_event = TriggerEventArray; + emEvent **sentinel_event = &TriggerEventArray[40]; + float x = position->x; + float y = position->y; + float z = position->z; + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); + + if (user_info && user_info->pEventTriggerPack) { + EventTriggerPack *trigger_pack = user_info->pEventTriggerPack; + vAABBTree *tree = trigger_pack->EventTree; + vAABB *aabb = tree->QueryLeaf(x, y, z); + if (aabb) { + EventTrigger *root_event = trigger_pack->EventTriggerArray; + int num_hits = -aabb->NumChildren; + + for (int i = 0; i < num_hits && current_event < sentinel_event; i++) { + EventTrigger *event = &root_event[aabb->ChildrenIndicies[i]]; + float event_x = event->PositionX; + float event_z = event->PositionZ; + float event_y = event->PositionY; + float dz = bAbs(z - event_z); + float dy = bAbs(y - event_y); + float dx = bAbs(x - event_x); + float r2 = event->GetRadius(); + float dist2 = dz * dz + dx * dx + dy * dy; + + r2 *= r2; + if (dist2 < r2) { + emEvent *new_event = emAddEvent(static_cast(event->GetEventID())); + new_event->pEventTrigger = event; + *current_event = new_event; + current_event++; + } + } + } + } + + if (current_event == TriggerEventArray) { + return 0; + } + + *current_event = 0; + return TriggerEventArray; +} + +void emProcessAllEvents() { + bTList temp_event_queue; + bTList locked_event_queue; + emEvent *event; + + CurrentEventQueue = &temp_event_queue; + event = MasterEventQueue.GetHead(); + + while (event != MasterEventQueue.EndOfList()) { + emEvent *next_event = event->GetNext(); + int event_id = event->ID; + int event_handled = 0; + + CurrentlyHandlingEvent = event; + + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + int handler_stream_mask = handler->StreamMask; + + if ((event_id & handler_stream_mask) != 0) { + unsigned int start_time = bGetTicker(); + + handler->HandlerFunction(event); + handler->TotalTime += bGetTickerDifference(start_time, bGetTicker()); + event_handled = 1; + } + } + + CurrentlyHandlingEvent = 0; + event->Remove(); + if (event->ReferenceCount == 0) { + delete event; + } else { + locked_event_queue.AddTail(event); + } + + event = next_event; + } + + while (!locked_event_queue.IsEmpty()) { + MasterEventQueue.AddTail(locked_event_queue.RemoveHead()); + } + while (!temp_event_queue.IsEmpty()) { + MasterEventQueue.AddTail(temp_event_queue.RemoveHead()); + } + + CurrentEventQueue = &MasterEventQueue; +} diff --git a/src/Speed/Indep/Src/World/ParameterMaps.cpp b/src/Speed/Indep/Src/World/ParameterMaps.cpp index e69de29bb..7d3f81892 100644 --- a/src/Speed/Indep/Src/World/ParameterMaps.cpp +++ b/src/Speed/Indep/Src/World/ParameterMaps.cpp @@ -0,0 +1,4 @@ +#include "ParameterMaps.hpp" + +ParameterAccessorBlendByDistance TintSunRiseAccessor[2] = {"Screen Tint SunRise", "Screen Tint SunRise"}; +ParameterAccessorBlendByDistance TintMiddayAccessor[2] = {"Screen Tint Midday", "Screen Tint Midday"}; diff --git a/src/Speed/Indep/Src/World/ParameterMaps.hpp b/src/Speed/Indep/Src/World/ParameterMaps.hpp index 761f31502..e2999f03a 100644 --- a/src/Speed/Indep/Src/World/ParameterMaps.hpp +++ b/src/Speed/Indep/Src/World/ParameterMaps.hpp @@ -5,6 +5,69 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bList.hpp" +class ParameterMapLayer; + +class ParameterAccessor : public bTNode { + public: + ParameterAccessor(); + ParameterAccessor(const char *layer_name); + virtual ~ParameterAccessor(); + + virtual void CaptureData(float x, float y); + virtual void ClearData(); + virtual float GetDataFloat(int field_index); + virtual int GetDataInt(int field_index); + + protected: + virtual void SetUpForNewLayer(); + + ParameterMapLayer *Layer; + unsigned int AutoAttachLayerNamehash; + const char *DebugName; + void *CurrentParameterData; +}; + +class ParameterAccessorBlend : public ParameterAccessor { + public: + ParameterAccessorBlend(); + ParameterAccessorBlend(const char *layer_name); + virtual ~ParameterAccessorBlend(); + + virtual void CaptureData(float x, float y, float ratio); + virtual void ClearData(); + + protected: + virtual void SetUpForNewLayer(); + + void *LastData; + int HaveLastData; + + private: + virtual void CaptureData(float x, float y); +}; + +class ParameterAccessorBlendByDistance : public ParameterAccessorBlend { + public: + ParameterAccessorBlendByDistance(); + ParameterAccessorBlendByDistance(const char *layer_name); + virtual ~ParameterAccessorBlendByDistance(); + + virtual void CaptureData(float x, float y, float full_blend_distance); + + protected: + virtual void SetUpForNewLayer(); + + float last_x; + float last_y; + int HaveLastPosition; + + private: + virtual void CaptureData(float x, float y); +}; + +extern ParameterAccessorBlendByDistance TintSunRiseAccessor[2]; +extern ParameterAccessorBlendByDistance TintMiddayAccessor[2]; #endif diff --git a/src/Speed/Indep/Src/World/Rain.hpp b/src/Speed/Indep/Src/World/Rain.hpp index cd6926519..5d6334c36 100644 --- a/src/Speed/Indep/Src/World/Rain.hpp +++ b/src/Speed/Indep/Src/World/Rain.hpp @@ -7,6 +7,100 @@ #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +// total size: 0x47C +struct Rain { + OnScreenRain OSrain; // offset 0x0, size 0x4 + int NoRain; // offset 0x4, size 0x4 + int NoRainAhead; // offset 0x8, size 0x4 + int inTunnel; // offset 0xC, size 0x4 + int inOverpass; // offset 0x10, size 0x4 + void *the_zone; // offset 0x14, size 0x4 + bVector3 outvex; // offset 0x18, size 0x10 + bVector2 twoDpos; // offset 0x28, size 0x8 + bVector3 CamVelLOCAL; // offset 0x30, size 0x10 + CurtainStatus IsValidRainCurtainPos; // offset 0x40, size 0x4 + int renderCount; // offset 0x44, size 0x4 + private: + eView *MyView; // offset 0x48, size 0x4 + float intensity; // offset 0x4C, size 0x4 + float CloudIntensity; // offset 0x50, size 0x4 + uint32 DesiredActive; // offset 0x54, size 0x4 + uint32 NewSwapBuffer; // offset 0x58, size 0x4 + uint32 OldSwapBuffer; // offset 0x5C, size 0x4 + int32 NumRainPoints; // offset 0x60, size 0x4 + unsigned int NumOfType[2]; // offset 0x64, size 0x8 + unsigned int DesiredNumOfType[2]; // offset 0x6C, size 0x8 + float Percentages[2]; // offset 0x74, size 0x8 + float precipWindEffect[2][2]; // offset 0x7C, size 0x10 + TextureInfo *texture_info[2]; // offset 0x8C, size 0x8 + bVector3 OldCarDirection; // offset 0x94, size 0x10 + bVector3 precipRadius[2]; // offset 0xA4, size 0x20 + bVector3 precipSpeedRange[2]; // offset 0xC4, size 0x20 + float precipZconstant[2]; // offset 0xE4, size 0x8 + RainWindType windType[2]; // offset 0xEC, size 0x8 + float CameraSpeed; // offset 0xF4, size 0x4 + bVector3 windSpeed; // offset 0xF8, size 0x10 + bVector3 DesiredwindSpeed; // offset 0x108, size 0x10 + float DesiredWindTime; // offset 0x118, size 0x4 + float windTime; // offset 0x11C, size 0x4 + uint32 fogR; // offset 0x120, size 0x4 + uint32 fogG; // offset 0x124, size 0x4 + uint32 fogB; // offset 0x128, size 0x4 + bVector2 aabbMax; // offset 0x12C, size 0x8 + bVector2 aabbMin; // offset 0x134, size 0x8 + ePoly PRECIPpoly[2]; // offset 0x13C, size 0x128 + bMatrix4 local2world; // offset 0x264, size 0x40 + bMatrix4 world2localrot; // offset 0x2A4, size 0x40 + float LenModifier; // offset 0x2E4, size 0x4 + float DesiredIntensity; // offset 0x2E8, size 0x4 + float DesiredCloudyness; // offset 0x2EC, size 0x4 + float DesiredRoadDampness; // offset 0x2F0, size 0x4 + float RoadDampness; // offset 0x2F4, size 0x4 + float percentPrecip[2]; // offset 0x2F8, size 0x8 + bVector3 PrevailingWindSpeed; // offset 0x300, size 0x10 + float WeatherTime; // offset 0x310, size 0x4 + float DesiredWeatherTime; // offset 0x314, size 0x4 + bVector3 Velocities[10][2]; // offset 0x318, size 0x140 + bVector2 ent0; // offset 0x458, size 0x8 + bVector2 ent1; // offset 0x460, size 0x8 + bVector2 ext0; // offset 0x468, size 0x8 + bVector2 ext1; // offset 0x470, size 0x8 + uint8 entFLAG; // offset 0x478, size 0x1 + uint8 extFLAG; // offset 0x479, size 0x1 + + public: + Rain(eView *view, RainType StartType); + void Init(RainType type, float percent); + + float GetRainIntensity() { + return intensity; + } + + float GetCloudIntensity() { + return CloudIntensity; + } + + float GetRoadDampness() { + return RoadDampness; + } + + void SetPrecipFogColour(unsigned int r, unsigned int g, unsigned int b) {} + + float GetAmount(RainType type) {} + + void SetRoadDampness(float damp) {} + + bVector3 *GetWind() {} + + void GetPrecipFogColour(unsigned int *r, unsigned int *g, unsigned int *b) { + *r = this->fogR; + *g = this->fogG; + *b = this->fogB; + } + + void AttachRainCurtain(float x0, float y0, float z0, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3); +}; + int AmIinATunnel(eView *view, int CheckOverPass); int AmIinATunnelSlow(eView *view, int CheckOverPass); void SetRainBase(); diff --git a/src/Speed/Indep/Src/World/Scenery.cpp b/src/Speed/Indep/Src/World/Scenery.cpp index e69de29bb..f3ffaef58 100644 --- a/src/Speed/Indep/Src/World/Scenery.cpp +++ b/src/Speed/Indep/Src/World/Scenery.cpp @@ -0,0 +1,1331 @@ +#include "Scenery.hpp" + +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "VisibleSection.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Ecstasy/eLight.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/World/TrackInfo.hpp" +#include "Speed/Indep/Src/World/Rain.hpp" +#include "Speed/Indep/bWare/Inc/SpeedScript.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" + +SceneryOverrideInfo *GetSceneryOverrideInfo(int override_info_number); +int LoaderSceneryGroup(bChunk *chunk); +int UnloaderSceneryGroup(bChunk *chunk); +int LoaderScenery(bChunk *chunk); +int UnloaderScenery(bChunk *chunk); + +struct _type_map; +typedef UTL::Std::map ModelHeirarchyMap; + +struct eSceneryLightContext : public eLightContext { + char Name[34]; + short LightingContextNumber; + bMatrix4 *LocalLights; + unsigned int NumLights; + + void EndianSwap() { + bPlatEndianSwap(&Type); + bPlatEndianSwap(&NumLights); + bEndianSwap16(&LightingContextNumber); + } +}; + +class PrecullerBooBooManager { + private: + unsigned char BitField[0x800]; + + public: + void Reset() { + bMemSet(this, 0, 0x800); + } + + int GetSectionNumber(bVector3 &position); + unsigned char *GetByte(int section_number); + unsigned char GetBit(int section_number); + + void Set(bVector3 &pos) { + int n = GetSectionNumber(pos); + unsigned char *p = GetByte(n); + *p |= GetBit(n); + } + + void Clr(bVector3 &pos) { + int n = GetSectionNumber(pos); + unsigned char *p = GetByte(n); + *p &= -GetBit(n) - 1U; + } + + bool IsSet(bVector3 &pos) { + int n = GetSectionNumber(pos); + unsigned char *p = GetByte(n); + return (*p & GetBit(n)) != 0; + } +}; + +struct GrandSceneryCullInfo { + // total size: 0x8E0 + SceneryCullInfo SceneryCullInfos[12]; // offset 0x0, size 0x8D0 + int NumCullInfos; // offset 0x8D0, size 0x4 + SceneryDrawInfo *pFirstDrawInfo; // offset 0x8D4, size 0x4 + SceneryDrawInfo *pCurrentDrawInfo; // offset 0x8D8, size 0x4 + SceneryDrawInfo *pTopDrawInfo; // offset 0x8DC, size 0x4 + + static SceneryDrawInfo SceneryDrawInfoTable[3500]; + + int WhatSectionsShouldWeDraw(short *sections_to_draw, int max_sections_to_draw, SceneryCullInfo *scenery_cull_info); + void CullView(SceneryCullInfo *scenery_cull_info); + void DoCulling(); + void StuffScenery(eView *view, int stuff_flags); +}; + +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern float EnvMapShadowExtraHeight; +extern eModel *pDebugModel; +extern PrecullerBooBooManager gPrecullerBooBooManager; +static const float EnablePrecullingSpeed = 40.0f * 0.4470272660255432f; +extern int PrecullerMode; +extern int DisablePrecullerCounter; +extern int RealTimeFrames; +extern int CurrentZoneNumber; +extern int SeeulatorToolActive; +extern int ScenerySectionToBlink; +extern int SeeulatorRefreshTrackStreamer; +extern int ShowSectionBoarder; +void RefreshTrackStreamer(); +void CreateWindRotMatrix(eView *view, bMatrix4 *matrix, int x, const bMatrix4 *world); +void RenderVisibleSectionBoundary(VisibleSectionBoundary *boundary, eView *view); +ScenerySectionHeader *GetScenerySectionHeader(int section_number); +int IsInTable(short *section_numbers, int num_sections, int section_number); +int ToggleIsInTable(short *section_numbers, int num_sections, int max_sections, int section_number); +bTList ScenerySectionHeaderList; +RegionQuery RegionInfo; +ModelHeirarchyMap HeirarchyMap; +bTList SceneryGroupList; +bChunkLoader bChunkLoaderSceneryGroup(0x34109, LoaderSceneryGroup, UnloaderSceneryGroup); +bChunkLoader bChunkLoaderScenerySection(0x80034100, LoaderScenery, UnloaderScenery); +bChunkLoader bChunkLoaderOverrideInfos(0x34108, LoaderScenery, UnloaderScenery); +bChunkLoader bChunkLoaderSceneryHeirarchy(0x8003410B, LoaderScenery, UnloaderScenery); +bChunkLoader bChunkLoaderSceneryLighting(0x80034115, LoaderScenery, UnloaderScenery); +SceneryDetailLevel ForceAllSceneryDetailLevels = SCENERY_DETAIL_NONE; +SceneryOverrideInfo *SceneryOverrideInfoTable = 0; +int NumSceneryOverrideInfos = 0; +eLight *LightTable = 0; +int MaxSceneryLightContexts = 0; +eSceneryLightContext **SceneryLightContextTable = 0; +void (*ModelConnectionCallback)(ScenerySectionHeader *, int, eModel *) = 0; +void (*ModelDisconnectionCallback)(ScenerySectionHeader *, int, eModel *) = 0; +void (*SectionConnectionCallback)(ScenerySectionHeader *) = 0; +void (*SectionDisconnectionCallback)(ScenerySectionHeader *) = 0; +eModel *pVisibleZoneBoundaryModel = 0; +short SceneryOverrideHashTable[257]; +SceneryDrawInfo GrandSceneryCullInfo::SceneryDrawInfoTable[3500]; +extern unsigned char SceneryGroupEnabledTable[0x1000]; + +inline int PrecullerBooBooManager::GetSectionNumber(bVector3 &position) { + int x_section = (static_cast(static_cast(position.x)) >> 5) & 0x7F; + return ((static_cast(position.y) & 0xFE0) << 2) | x_section; +} + +static inline int GetPrecullerSectionNumber(float x, float y) { + return ((static_cast(static_cast(x)) >> 5) & 0x1F) + (static_cast(y) & 0x3E0); +} + +unsigned char *PrecullerBooBooManager::GetByte(int section_number) { + return BitField + (section_number >> 3); +} + +unsigned char PrecullerBooBooManager::GetBit(int section_number) { + return static_cast(1 << (section_number & 7)); +} + +static inline void EndianSwapSectionHeader_Scenery(int *section_header_words) { + bEndianSwap32(reinterpret_cast(section_header_words) + 0xC); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x10); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x14); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x38); +} + +static inline void EndianSwapSceneryInfo_Scenery(unsigned char *data) { + for (int i = 0; i < 4; i++) { + bEndianSwap32(data + 0x18 + i * 4); + } + bEndianSwap32(data + 0x38); + bEndianSwap32(data + 0x3C); + bEndianSwap32(data + 0x40); +} + +static inline void EndianSwapSceneryInstance_Scenery(SceneryInstance *instance) { + bPlatEndianSwap(&instance->ExcludeFlags); + bPlatEndianSwap(&instance->PrecullerInfoIndex); + bPlatEndianSwap(&instance->LightingContextNumber); + for (int i = 0; i < 3; i++) { + bPlatEndianSwap(&instance->Position[i]); + } + for (int i = 0; i < 9; i++) { + bPlatEndianSwap(&instance->Rotation[i]); + } + bPlatEndianSwap(&instance->SceneryInfoNumber); + for (int i = 0; i < 3; i++) { + bPlatEndianSwap(&instance->BBoxMin[i]); + bPlatEndianSwap(&instance->BBoxMax[i]); + } +} + +static inline void EndianSwapPrecullerInfo_Scenery(unsigned char *data) { + bEndianSwap32(data + 0x00); + bEndianSwap32(data + 0x04); + bEndianSwap32(data + 0x08); + bEndianSwap32(data + 0x0C); + bEndianSwap32(data + 0x10); + bEndianSwap32(data + 0x14); + bEndianSwap16(data + 0x18); + for (int i = 0; i < 5; i++) { + bEndianSwap16(data + 0x1A + i * 2); + } +} + +static inline SceneryOverrideInfo *FindMatchingOverrideInfo_Scenery(int section_number, int override_index) { + for (int i = 0; i < NumSceneryOverrideInfos; i++) { + SceneryOverrideInfo *override_info = &SceneryOverrideInfoTable[i]; + if (override_info->SectionNumber == section_number && override_info->InstanceNumber == override_index) { + return override_info; + } + } + return 0; +} + +static inline eModel *FindExistingModel_Scenery(unsigned char *scenery_info, int model_slot) { + unsigned int name_hash = *reinterpret_cast(scenery_info + 0x18 + model_slot * 4); + for (int i = 0; i < model_slot; i++) { + eModel *model = *reinterpret_cast(scenery_info + 0x28 + i * 4); + if (model && model->NameHash == name_hash) { + return model; + } + } + return 0; +} + +static inline unsigned char *GetSceneryInfo_Scenery(int *section_header_words, short scenery_info_number) { + return reinterpret_cast(section_header_words[6]) + scenery_info_number * 0x48; +} + +static inline eModel *GetSceneryModel_Scenery(unsigned char *scenery_info, int model_slot) { + return *reinterpret_cast(scenery_info + 0x28 + model_slot * 4); +} + +static inline float GetSceneryRadius_Scenery(unsigned char *scenery_info) { + return *reinterpret_cast(scenery_info + 0x38); +} + +static inline int InlinedViewGetPixelSize(SceneryCullInfo *scenery_cull_info, const bVector3 *position, float radius) { + bVector3 dir = *position - scenery_cull_info->Position; + float distance_ahead = bDot(&dir, &scenery_cull_info->Direction); + if (distance_ahead < -radius) { + return 0; + } + + float distance_away = bLength(&dir); + float pixel_size_float = scenery_cull_info->H; + float distance_minus_radius = distance_away - radius; + if (distance_minus_radius > radius) { + pixel_size_float = (radius * pixel_size_float) / distance_minus_radius; + } + return static_cast(pixel_size_float); +} + +static inline bMatrix4 *eFrameMallocMatrix(int num_matrices) { + unsigned char *address = CurrentBufferPos; + unsigned int size = num_matrices * sizeof(bMatrix4); + unsigned char *next_buffer_pos = address + size; + if (CurrentBufferEnd <= next_buffer_pos) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + address = 0; + } else { + CurrentBufferPos = next_buffer_pos; + } + return reinterpret_cast(address); +} + +void BuildSceneryOverrideHashTable() { + int num_scenery_override_infos = NumSceneryOverrideInfos; + SceneryOverrideInfo *scenery_override_info_table = SceneryOverrideInfoTable; + int index = 0; + unsigned int i = 0; + do { + unsigned int next_i = i + 1; + SceneryOverrideHashTable[i] = static_cast(index); + while (index < num_scenery_override_infos && + (static_cast(reinterpret_cast(&scenery_override_info_table[index])[0]) & 0xFF) == i) { + index += 1; + } + i = next_i; + } while (static_cast(i) < 0x100); + SceneryOverrideHashTable[0x100] = static_cast(index); +} + +ModelHeirarchy *FindSceneryHeirarchyByName(unsigned int name_hash) { + ModelHeirarchyMap::iterator it = HeirarchyMap.find(name_hash); + if (it == HeirarchyMap.end()) { + return 0; + } + return it->second; +} + +SceneryOverrideInfo *GetSceneryOverrideInfo(int override_info_number) { + return reinterpret_cast(reinterpret_cast(SceneryOverrideInfoTable) + override_info_number * 6); +} + +void SceneryOverrideInfo::AssignOverrides() { + ScenerySectionHeader *section_header = GetScenerySectionHeader(SectionNumber); + if (section_header) { + AssignOverrides(section_header); + } +} + +void SceneryOverrideInfo::AssignOverrides(ScenerySectionHeader *section_header) { + SceneryInstance *scenery_instance = section_header->GetSceneryInstance(InstanceNumber); + + if ((scenery_instance->ExcludeFlags & 0x800000) != 0 && ((scenery_instance->ExcludeFlags ^ ExcludeFlags) & 0x400) != 0) { + bMatrix4 matrix; + bMatrix4 flip_matrix; + + scenery_instance->GetRotation(&matrix); + + bIdentity(&flip_matrix); + flip_matrix.v0.x = -1.0f; + bMulMatrix(&matrix, &matrix, &flip_matrix); + scenery_instance->GetPosition(&matrix.v3); + scenery_instance->SetMatrix(&matrix); + } + + scenery_instance->ExcludeFlags = ExcludeFlags + (scenery_instance->ExcludeFlags & 0xFFFF0000); +} + +int LoaderSceneryGroup(bChunk *chunk) { + if (chunk->GetID() == 0x34109) { + int chunk_size = chunk->Size; + int group_offset = 0; + if (group_offset < chunk_size) { + do { + SceneryGroup *group = reinterpret_cast(reinterpret_cast(chunk) + group_offset + 8); + SceneryGroup *head = SceneryGroupList.GetHead(); + head->Prev = group; + group->Next = head; + SceneryGroupList.HeadNode.Next = group; + group->Prev = reinterpret_cast(&SceneryGroupList); + + bEndianSwap32(&group->NameHash); + bEndianSwap16(&group->GroupNumber); + bEndianSwap16(&group->NumObjects); + for (int i = 0; i < group->NumObjects; i++) { + bEndianSwap16(&group->OverrideInfoNumbers[i]); + } + + group_offset += (group->NumObjects * sizeof(unsigned short) + 0x17U) & 0xFFFFFFFC; + } while (group_offset < chunk_size); + } + return 1; + } + + return 0; +} + +int UnloaderSceneryGroup(bChunk *chunk) { + if (chunk->GetID() == 0x34109) { + bMemSet(SceneryGroupEnabledTable, 0, 0x1000); + SceneryGroupEnabledTable[0] = 1; + SceneryGroupList.InitList(); + return 1; + } + + return 0; +} + +SceneryGroup *FindSceneryGroup(unsigned int name_hash) { + for (SceneryGroup *group = SceneryGroupList.GetHead(); group != SceneryGroupList.EndOfList(); group = group->GetNext()) { + if (group->NameHash == name_hash) { + return group; + } + } + return 0; +} + +void EnableSceneryGroup(unsigned int name_hash, bool flip_artwork) { + SceneryGroup *group = static_cast(FindSceneryGroup(name_hash)); + if (group) { + unsigned short override_flags = 0; + if (flip_artwork) { + override_flags = 0x400; + } + + for (int i = 0; i < group->NumObjects; i++) { + SceneryOverrideInfo *override_info = group->GetOverrideInfo(i); + override_info->ExcludeFlags = override_flags | (override_info->ExcludeFlags & 0xFBEF); + override_info->AssignOverrides(); + } + + SceneryGroupEnabledTable[group->GroupNumber] = 1; + if (flip_artwork) { + SceneryGroupEnabledTable[group->GroupNumber] |= 2; + } + if (group->DriveThroughBarrierFlag) { + SceneryGroupEnabledTable[group->GroupNumber] |= 4; + } + } +} + +void DisableSceneryGroup(unsigned int name_hash) { + SceneryGroup *group = static_cast(FindSceneryGroup(name_hash)); + if (group) { + for (int i = 0; i < group->NumObjects; i++) { + SceneryOverrideInfo *override_info = group->GetOverrideInfo(i); + override_info->ExcludeFlags = override_info->ExcludeFlags | 0x10; + override_info->AssignOverrides(); + } + SceneryGroupEnabledTable[group->GroupNumber] = 0; + } +} + +void DisableAllSceneryGroups() { + for (SceneryGroup *group = SceneryGroupList.GetHead(); group != SceneryGroupList.EndOfList(); group = group->GetNext()) { + if (SceneryGroupEnabledTable[group->GroupNumber]) { + for (int i = 0; i < group->NumObjects; i++) { + SceneryOverrideInfo *override_info = group->GetOverrideInfo(i); + override_info->ExcludeFlags = override_info->ExcludeFlags | 0x10; + override_info->AssignOverrides(); + } + SceneryGroupEnabledTable[group->GroupNumber] = 0; + } + } +} + +void InitVisibleZones() { + if (pVisibleZoneBoundaryModel == 0) { + eModel *model = reinterpret_cast(bOMalloc(eModelSlotPool)); + unsigned int name_hash = bStringHash("MARKER_BOUNDARY"); + model->NameHash = 0; + model->Solid = 0; + model->Init(name_hash); + pVisibleZoneBoundaryModel = model; + } +} + +void CloseVisibleZones() { + eModel *model = pVisibleZoneBoundaryModel; + if (pVisibleZoneBoundaryModel) { + pVisibleZoneBoundaryModel->UnInit(); + bFree(eModelSlotPool, model); + } + pVisibleZoneBoundaryModel = 0; + if (SeeulatorToolActive) { + int data = 0; + bFunkCallASync("Seeulator", 4, &data, 4); + bFunkCallASync("Seeulator", 5, &data, 4); + bFunkCallASync("Seeulator", 6, &data, 4); + } +} + +void ServicePreculler() {} + +void LoadPrecullerBooBooScript(const char *filename, bool reset) { + if (reset) { + gPrecullerBooBooManager.Reset(); + } + + SpeedScript script(filename, 1); + while (script.GetNextCommand("BOOBOO:")) { + if (bStrICmp(script.GetNextArgumentString(), TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { + script.GetNextArgumentString(); + char *option = script.GetNextArgumentString(); + bool set_booboo = bStrICmp(option, "SET") == 0; + bool clr_booboo = bStrICmp(option, "CLR") == 0; + script.GetNextArgumentString(); + bVector3 pos = script.GetNextArgumentVector3(); + if (set_booboo) { + gPrecullerBooBooManager.Set(pos); + } else if (clr_booboo) { + gPrecullerBooBooManager.Clr(pos); + } + } + } +} + +void LoadPrecullerBooBooScripts() { + LoadPrecullerBooBooScript("TRACKS\\PrecullerBooBooScript.hoo", 1); +} + +SceneryInfo *FindSceneryInfo(unsigned int name_hash) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = reinterpret_cast(section_header->GetNext())) { + int *section_header_words = reinterpret_cast(section_header); + for (int i = 0; i < section_header_words[7]; i++) { + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + i; + eModel *model = scenery_info->pModel[0]; + if (model && model->NameHash == name_hash) { + return scenery_info; + } + } + } + return 0; +} + +SceneryInstance *FindSceneryInstance(unsigned int name_hash) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = reinterpret_cast(section_header->GetNext())) { + int *section_header_words = reinterpret_cast(section_header); + for (int i = 0; i < section_header_words[9]; i++) { + SceneryInstance *instance = reinterpret_cast(section_header_words[8]) + i; + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + instance->SceneryInfoNumber; + eModel *model = scenery_info->pModel[0]; + if (model && model->NameHash == name_hash) { + return instance; + } + } + } + return 0; +} + +void ScenerySectionHeader::DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state) { + int *section_header_words = reinterpret_cast(this); + SceneryInstance *instance = GetSceneryInstance(scenery_instance_number); + tPrecullerInfo *preculler_info = GetPrecullerInfo(instance->PrecullerInfoIndex); + int preculler_section_number = scenery_cull_info->PrecullerSectionNumber; + if (preculler_section_number >= 0) { + int byte_number = preculler_section_number >> 3; + int bit_number = preculler_section_number & 7; + unsigned char visibility_bits = preculler_info->GetBits()[byte_number]; + int visibility_mask = 1 << bit_number; + if ((visibility_bits & visibility_mask) != 0) { + return; + } + } + + unsigned char instance_exclude_flags = instance->ExcludeFlags; + short scenery_info_number = instance->SceneryInfoNumber; + if (((instance_exclude_flags ^ 0x60) & scenery_cull_info->ExcludeFlags) != 0) { + return; + } + int pixel_size_int; + SceneryInfo *scenery_info = reinterpret_cast(GetSceneryInfo_Scenery(section_header_words, scenery_info_number)); + + if (visibility_state == EVISIBLESTATE_PARTIAL) { + bVector3 bbox_min; + bVector3 bbox_max; + instance->GetBBox(&bbox_min, &bbox_max); + visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); + if (visibility_state == EVISIBLESTATE_NOT) { + return; + } + } + + float radius = scenery_info->Radius + 6.0f; + pixel_size_int = InlinedViewGetPixelSize(scenery_cull_info, instance->GetPosition(), radius); + + if (pixel_size_int < 2) { + return; + } + unsigned int instance_flags = instance->ExcludeFlags; + if ((instance_flags & 0x2000000) != 0) { + pixel_size_int += 10; + } + + eModel *model = 0; + if ((scenery_cull_info->ExcludeFlags & 0x1800) != 0) { + if ((instance_flags & 0x80) != 0) { + if (pixel_size_int > 0x1F) { + model = scenery_info->pModel[2]; + } + } else { + if (pixel_size_int > 0x1F) { + if ((instance_flags & 0x1000100) != 0) { + model = scenery_info->pModel[0]; + } else { + model = scenery_info->pModel[3]; + } + } + } + } else if ((scenery_cull_info->ExcludeFlags & 0x20) != 0) { + if (pixel_size_int > 0x1F) { + model = scenery_info->pModel[2]; + } + } else if (eGetCurrentViewMode() > EVIEWMODE_ONE_RVM) { + if (pixel_size_int > 0x16) { + model = scenery_info->pModel[2]; + } + } else if (pixel_size_int > 0x11) { + model = scenery_info->pModel[0]; + eSolid *solid = model ? model->GetSolid() : 0; + if (solid && solid->NumPolys > 0x27) { + float lod_scale = solid->Density; + if (lod_scale < 6.0f) { + lod_scale = 6.0f; + } + if ((static_cast(pixel_size_int) / lod_scale) < 8.7f) { + model = scenery_info->pModel[2]; + } + } + } + + if (!model) { + return; + } + + if ((instance->ExcludeFlags & 0x200) != 0) { + SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; + if (draw_info >= scenery_cull_info->pTopDrawInfo) { + return; + } + + scenery_cull_info->pCurrentDrawInfo = draw_info + 1; + draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); + draw_info->pMatrix = 0; + draw_info->SceneryInst = instance; + return; + } + + bMatrix4 *matrix = eFrameMallocMatrix(1); + + if (!matrix) { + return; + } + + instance->GetRotation(matrix); + bFill(&matrix->v3, instance->Position[0], instance->Position[1], instance->Position[2], 1.0f); + + if ((instance->ExcludeFlags & scenery_cull_info->ExcludeFlags & 0x100) != 0) { + matrix->v3.z += EnvMapShadowExtraHeight; + } + if ((scenery_cull_info->ExcludeFlags & 0x800) != 0) { + matrix->v2.z = -matrix->v2.z; + } + + SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; + if (draw_info >= scenery_cull_info->pTopDrawInfo) { + return; + } + + scenery_cull_info->pCurrentDrawInfo = draw_info + 1; + draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); + if ((scenery_cull_info->ExcludeFlags & 0x4000) != 0 && model->GetSolid() && (model->GetSolid()->Flags & 0x80) != 0) { + bMatrix4 windrot; + int offset = static_cast(matrix->v3.x * 60.0f) % 0x168; + CreateWindRotMatrix(scenery_cull_info->pView, &windrot, offset, matrix); + bMulMatrix(matrix, matrix, &windrot); + } + + draw_info->pMatrix = matrix; + draw_info->SceneryInst = instance; + + if (scenery_cull_info->pView == eGetView(1, false) || scenery_cull_info->pView == eGetView(2, false)) { + ePositionMarker *position_marker = 0; + while ((position_marker = model->GetPostionMarker(position_marker)) != 0) { + if (model->GetSolid()) { + unsigned int exclude_view_ids = 2; + if (scenery_cull_info->pView == eGetView(1, false)) { + exclude_view_ids = 1; + } + + eLightFlare *light_flare = eGetNextLightFlareInPool(exclude_view_ids); + if (light_flare) { + bVector4 ps; + ps.x = position_marker->Matrix.v3.x - model->GetSolid()->PivotMatrix.v3.x; + ps.y = position_marker->Matrix.v3.y - model->GetSolid()->PivotMatrix.v3.y; + ps.z = position_marker->Matrix.v3.z - model->GetSolid()->PivotMatrix.v3.z; + ps.w = 1.0f; + eMulVector(&ps, draw_info->pMatrix, &ps); + + if (scenery_cull_info->pView->Precipitation && 0.0f < scenery_cull_info->pView->Precipitation->GetRoadDampness() && + (light_flare->PositionX != ps.x || light_flare->PositionY != ps.y || light_flare->PositionZ != ps.z)) { + light_flare->ReflectPosZ = 999.0f; + } + + light_flare->PositionX = ps.x; + light_flare->PositionY = ps.y; + light_flare->PositionZ = ps.z; + light_flare->Type = position_marker->iParam0 + 14; + if (static_cast(position_marker->iParam0 - 3) < 3) { + bVector2 dr; + light_flare->Flags = 4; + dr.x = ps.x - draw_info->pMatrix->v3.x; + dr.y = ps.y - draw_info->pMatrix->v3.y; + bNormalize(&dr, &dr); + light_flare->DirectionX = dr.x; + light_flare->DirectionY = dr.y; + light_flare->DirectionZ = 0.0f; + } else { + light_flare->Flags = 2; + } + } + } + } + } +} + +void ScenerySectionHeader::TreeCull(SceneryCullInfo *scenery_cull_info) { + const int max_depth = 64; + SceneryTreeNode *node_stack[max_depth]; + unsigned char visibility_state_stack[max_depth]; + SceneryTreeNode **pnode = node_stack + 1; + unsigned char *pvisibility_state = visibility_state_stack + 1; + + node_stack[0] = reinterpret_cast(reinterpret_cast(this)[10]); + visibility_state_stack[0] = 1; + while (pnode != node_stack) { + pnode -= 1; + SceneryTreeNode *node = *pnode; + pvisibility_state -= 1; + unsigned char visibility_state = *pvisibility_state; + if (visibility_state == 1) { + bVector3 bbox_min; + bVector3 bbox_max; + + node->GetBBox(&bbox_min, &bbox_max); + visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); + } + + if (visibility_state != 0) { + for (int child_number = 0; child_number < node->NumChildren; child_number++) { + // TODO + // short child_code = node->Children[child_number]; + short child_code; + if (child_code >= 0) { + DrawAScenery(child_code, scenery_cull_info, visibility_state); + } else { + int scenery_instance_number = child_code * -1; + SceneryTreeNode *child_node; + + child_node = reinterpret_cast(reinterpret_cast(reinterpret_cast(this)[10]) + + static_cast(scenery_instance_number) * 0x24); + + *pnode = child_node; + *pvisibility_state = visibility_state; + pnode += 1; + pvisibility_state += 1; + } + } + } + } +} + +int LoaderScenery(bChunk *chunk) { + if (chunk->GetID() == 0x34108) { + SceneryOverrideInfoTable = reinterpret_cast(chunk->GetData()); + NumSceneryOverrideInfos = static_cast(chunk->Size) / 6; + for (int i = 0; i < NumSceneryOverrideInfos; i++) { + SceneryOverrideInfo *override_info = &SceneryOverrideInfoTable[i]; + override_info->EndianSwap(); + } + BuildSceneryOverrideHashTable(); + return 1; + } + + if (chunk->GetID() == 0x80034100) { + ScenerySectionHeader *section_header = 0; + bChunk *last_chunk = chunk->GetLastChunk(); + + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + unsigned int subchunk_id = subchunk->GetID(); + if (subchunk_id == 0x34101) { + section_header = reinterpret_cast(subchunk->GetAlignedData(0x10)); + int *section_header_words = reinterpret_cast(section_header); + if (section_header_words[2] == 0) { + bEndianSwap32(reinterpret_cast(section_header_words) + 0xC); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x10); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x14); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x38); + } + + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.AllocateUserInfo(section_header_words[3]); + user_info->pScenerySectionHeader = section_header; + + if (GetScenerySectionLetter(section_header_words[3]) == 'Z') { + ScenerySectionHeaderList.AddHead(section_header); + } else { + ScenerySectionHeaderList.AddTail(section_header); + } + } else if (subchunk_id == 0x34102) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[6] = reinterpret_cast(subchunk->GetData()); + section_header_words[7] = static_cast(subchunk->Size) / 0x48; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[7]; i++) { + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x40)); + for (int n = 0; n < 4; n++) { + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x18 + n * 4)); + } + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x38)); + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x3C)); + } + } + + for (int i = 0; i < section_header_words[7]; i++) { + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + i; + unsigned int hierarchy_name = scenery_info->mHeirarchyNameHash; + if (hierarchy_name != 0) { + scenery_info->mHeirarchy = FindSceneryHeirarchyByName(hierarchy_name); + } + } + } else if (subchunk_id == 0x34103) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[8] = (reinterpret_cast(subchunk) + 0x17) & 0xFFFFFFF0; + section_header_words[9] = + static_cast(subchunk->Size - (section_header_words[8] - reinterpret_cast(subchunk->GetData()))) >> 6; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[9]; i++) { + unsigned char *instance = reinterpret_cast(section_header_words[8] + i * 0x40); + bEndianSwap16(instance + 0x3E); + bEndianSwap32(instance + 0x18); + for (int n = 0; n < 3; n++) { + unsigned char *swap = instance + n * 4; + bEndianSwap32(swap + 0x20); + } + for (int n = 0; n < 9; n++) { + unsigned char *swap = instance + n * 2; + bEndianSwap16(swap + 0x2C); + } + bEndianSwap32(instance + 0x00); + bEndianSwap32(instance + 0x04); + bEndianSwap32(instance + 0x08); + bEndianSwap32(instance + 0x0C); + bEndianSwap32(instance + 0x10); + bEndianSwap32(instance + 0x14); + bEndianSwap16(instance + 0x1C); + bEndianSwap16(instance + 0x1E); + } + } + } else if (subchunk_id == 0x34105) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[10] = reinterpret_cast(subchunk->GetData()); + section_header_words[11] = static_cast(subchunk->Size) / 0x24; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[11]; i++) { + bEndianSwap16(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x18)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x00)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x04)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x08)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x0C)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x10)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x14)); + for (int n = 0; n < 5; n++) { + bEndianSwap16(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x1A + n * 2)); + } + } + } + } else if (subchunk_id == 0x34106) { + int *section_header_words = reinterpret_cast(section_header); + int num_overrides = static_cast(subchunk->Size) >> 2; + unsigned short *override_data_base = reinterpret_cast(subchunk->GetData()); + for (int i = 0; i < num_overrides; i++) { + unsigned short *override_data = reinterpret_cast(reinterpret_cast(override_data_base) + i * 4); + bEndianSwap16(&override_data[0]); + bEndianSwap16(&override_data[1]); + SceneryOverrideInfo *override_info = + reinterpret_cast(reinterpret_cast(SceneryOverrideInfoTable) + override_data[1] * 6); + if (override_info->InstanceNumber == override_data[0] && override_info->SectionNumber == section_header_words[3]) { + override_info->AssignOverrides(section_header); + } + } + } else if (subchunk_id == 0x34107) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[12] = reinterpret_cast(subchunk->GetData()); + int num_override_datas = static_cast(subchunk->Size) >> 7; + section_header_words[13] = num_override_datas; + if (section_header_words[2] == 0) { + for (int i = 0; i < num_override_datas; i++) { + } + } + } + } + + if (!AreChunksBeingMoved()) { + int *section_header_words = reinterpret_cast(section_header); + SceneryInfo *scenery_infos = reinterpret_cast(section_header_words[6]); + for (int n = 0; n < section_header_words[7]; n++) { + SceneryInfo *scenery_info = &scenery_infos[n]; + for (int detail_level = 0; detail_level < 4; detail_level++) { + unsigned int name_hash = scenery_info->NameHash[detail_level]; + if (name_hash != 0 && name_hash != 0xBE43EDBB && name_hash != 0x90F70174) { + eModel *model = 0; + for (int i = 0; i < detail_level; i++) { + model = scenery_info->pModel[i]; + if (model && model->NameHash == name_hash) { + break; + } + model = 0; + } + + if (!model) { + model = reinterpret_cast(bOMalloc(eModelSlotPool)); + model->NameHash = 0; + model->Solid = 0; + model->Init(name_hash); + if (ModelConnectionCallback) { + ModelConnectionCallback(section_header, n, model); + } + } + scenery_info->pModel[detail_level] = model; + } + } + + eModel *lowest_detail_model = scenery_info->pModel[1]; + if (scenery_info->pModel[2] == 0 && lowest_detail_model != 0) { + scenery_info->pModel[2] = lowest_detail_model; + } + if (scenery_info->pModel[0] == 0 && lowest_detail_model != 0) { + scenery_info->pModel[0] = lowest_detail_model; + } + } + + if (SectionConnectionCallback) { + SectionConnectionCallback(section_header); + } + } + + reinterpret_cast(section_header)[2] = 1; + return 1; + } + + if (chunk->GetID() == 0x8003410B) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + ModelHeirarchy *mH = reinterpret_cast(subchunk->GetData()); + ModelHeirarchy::Node *node = reinterpret_cast(mH + 1); + bEndianSwap32(&mH->mNameHash); + + unsigned int num_models = mH->mNumNodes; + for (unsigned int i = 0; i < num_models; i++) { + bEndianSwap32(&node[i].mNodeName); + bEndianSwap32(&node[i].mModelHash); + } + + HeirarchyMap[mH->mNameHash] = mH; + for (unsigned int i = 0; i < num_models; i++) { + eModel *model = 0; + if (node[i].mModelHash != 0) { + model = reinterpret_cast(bOMalloc(eModelSlotPool)); + if (model) { + model->NameHash = 0; + model->Solid = 0; + model->Init(node[i].mModelHash); + } + } + node[i].mModel = model; + } + } + return 1; + } + + if (chunk->GetID() == 0x80034115) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + if (subchunk->GetID() == 0x34116) { + LightTable = reinterpret_cast(subchunk->GetData()); + } else if (subchunk->GetID() == 0x34117) { + SceneryLightContextTable = reinterpret_cast(subchunk->GetData()); + MaxSceneryLightContexts = (subchunk->Size + 3) >> 2; + } else if (subchunk->GetID() == 0x34118) { + eSceneryLightContext *light_context = reinterpret_cast(subchunk->GetAlignedData(0x10)); + bPlatEndianSwap(&light_context->Type); + bPlatEndianSwap(&light_context->NumLights); + bPlatEndianSwap(&light_context->LightingContextNumber); + light_context->LocalLights = reinterpret_cast(light_context + 1); + for (unsigned int i = 0; i < light_context->NumLights; i++) { + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x00)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x10)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x20)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x30)); + } + SceneryLightContextTable[light_context->LightingContextNumber] = light_context; + } + } + return 1; + } + + return 0; +} + +ScenerySectionHeader *GetScenerySectionHeader(int section_number) { + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); + if (!user_info) { + return 0; + } + return user_info->pScenerySectionHeader; +} + +int IsInTable(short *section_numbers, int num_sections, int section_number) { + for (int i = 0; i < num_sections; i++) { + if (section_numbers[i] == section_number) { + return i; + } + } + return -1; +} + +int ToggleIsInTable(short *section_numbers, int num_sections, int max_sections, int section_number) { + int section_index = IsInTable(section_numbers, num_sections, section_number); + if (section_index >= 0) { + section_numbers[section_index] = -1; + return num_sections; + } + + section_numbers[num_sections % max_sections] = static_cast(section_number); + return num_sections + 1; +} + +int UnloaderScenery(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + + if (chunk_id == 0x34108) { + SceneryOverrideInfoTable = 0; + NumSceneryOverrideInfos = 0; + BuildSceneryOverrideHashTable(); + return 1; + } + + if (chunk_id == 0x80034100) { + ScenerySectionHeader *section_header = reinterpret_cast(chunk->GetAlignedData(0x10)); + int *section_header_words = reinterpret_cast(section_header); + TheVisibleSectionManager.GetUserInfo(section_header_words[3])->pScenerySectionHeader = 0; + TheVisibleSectionManager.UnallocateUserInfo(section_header_words[3]); + section_header->Remove(); + + if (!AreChunksBeingMoved()) { + for (int i = 0; i < section_header_words[7]; i++) { + unsigned char *scenery_info = reinterpret_cast(section_header_words[6]) + i * 0x48; + eModel **model_slots = reinterpret_cast(scenery_info + 0x28); + for (int j = 0; j < 4; j++) { + if (AreChunksBeingMoved()) { + break; + } + + if (model_slots[j]) { + eModel *slot_model = model_slots[j]; + for (int k = j + 1; k < 4; k++) { + if (model_slots[k] == slot_model) { + model_slots[k] = 0; + } + } + if (ModelDisconnectionCallback) { + ModelDisconnectionCallback(section_header, i, model_slots[j]); + } + + eModel *model = model_slots[j]; + if (model) { + model->UnInit(); + bFree(eModelSlotPool, model); + } + model_slots[j] = 0; + } + } + } + + if (SectionDisconnectionCallback) { + SectionDisconnectionCallback(section_header); + } + } + + return 1; + } + + if (chunk_id == 0x8003410B) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + ModelHeirarchy *heirarchy = reinterpret_cast(subchunk->GetData()); + ModelHeirarchy::Node *nodes = reinterpret_cast(heirarchy + 1); + unsigned int num_models = heirarchy->mNumNodes; + + for (unsigned int i = 0; i < num_models; i++) { + eModel *model = nodes[i].mModel; + if (model) { + model->UnInit(); + bFree(eModelSlotPool, model); + nodes[i].mModel = 0; + } + } + + ModelHeirarchyMap::iterator it = HeirarchyMap.find(heirarchy->mNameHash); + if (it != HeirarchyMap.end()) { + HeirarchyMap.erase(it); + } + } + return 1; + } + + if (chunk_id == 0x80034115) { + MaxSceneryLightContexts = 0; + LightTable = 0; + SceneryLightContextTable = 0; + return 1; + } + + return 0; +} + +int GrandSceneryCullInfo::WhatSectionsShouldWeDraw(short *sections_to_draw, int max_sections_to_draw, SceneryCullInfo *scenery_cull_info) { + short *sections = sections_to_draw; + int max_sections = max_sections_to_draw; + SceneryCullInfo *cull_info = scenery_cull_info; + DrivableScenerySection *drivable_scenery_section; + int iViewID = cull_info->pView->GetID(); + if (iViewID == EVIEW_SHADOWMAP1 || iViewID == EVIEW_SHADOWMAP2) { + int iViewPlayer = iViewID - 12; + drivable_scenery_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(eViews[iViewPlayer].GetCamera()->GetPosition())); + } else { + drivable_scenery_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(cull_info->pView->GetCamera()->GetPosition())); + } + + int num_sections_to_draw = 0; + if (!drivable_scenery_section) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = section_header->GetNext()) { + int section_number = section_header->GetSectionNumber(); + int subsection_number = section_number % 100; + if (subsection_number < 10 && num_sections_to_draw < max_sections) { + sections[num_sections_to_draw] = static_cast(section_number); + num_sections_to_draw += 1; + } + } + } else { + int section_number = 0xA28; + section_number_loop: + if (section_number >= 0xA8C) { + goto end_section_number_loop; + } + + if (GetScenerySectionHeader(section_number) && num_sections_to_draw < max_sections) { + sections[num_sections_to_draw] = static_cast(section_number); + num_sections_to_draw += 1; + } + section_number += 1; + goto section_number_loop; + end_section_number_loop: + + for (int i = 0; i < drivable_scenery_section->GetNumVisibleSections(); i++) { + int section_number = drivable_scenery_section->GetVisibleSection(i); + if (section_number >= 0 && GetScenerySectionHeader(section_number) && num_sections_to_draw < max_sections) { + sections[num_sections_to_draw] = static_cast(section_number); + num_sections_to_draw += 1; + } + } + } + + if (cull_info->pView->GetID() == EVIEW_PLAYER1) { + int current_zone_number = -1; + if (drivable_scenery_section) { + current_zone_number = drivable_scenery_section->GetSectionNumber(); + } + + if (current_zone_number != CurrentZoneNumber) { + CurrentZoneNumber = current_zone_number; + if (!SeeulatorToolActive) { + return num_sections_to_draw; + } + if (drivable_scenery_section) { + bFunkCallASync("Seeulator", 1, &CurrentZoneNumber, 4); + } + } + + if (SeeulatorToolActive) { + if (ScenerySectionToBlink != 0 && ((RealTimeFrames / 5) & 1U) != 0) { + num_sections_to_draw = ToggleIsInTable(sections, num_sections_to_draw, max_sections, ScenerySectionToBlink); + } + if (SeeulatorToolActive && SeeulatorRefreshTrackStreamer != 0) { + RefreshTrackStreamer(); + SeeulatorRefreshTrackStreamer = 0; + } + } + } + + return num_sections_to_draw; +} + +void GrandSceneryCullInfo::CullView(SceneryCullInfo *scenery_cull_info) { + short sections_to_draw[128]; + int num_sections = WhatSectionsShouldWeDraw(sections_to_draw, 0x80, scenery_cull_info); + + for (int i = 0; i < num_sections; i++) { + if (sections_to_draw[i] >= 0) { + ScenerySectionHeader *section_header = GetScenerySectionHeader(sections_to_draw[i]); + if (section_header && reinterpret_cast(section_header)[10] != 0) { + section_header->TreeCull(scenery_cull_info); + } + } + } +} + +void GrandSceneryCullInfo::DoCulling() { + ProfileNode profile_node("TODO", 0); + int n; + pFirstDrawInfo = SceneryDrawInfoTable; + pCurrentDrawInfo = SceneryDrawInfoTable; + pTopDrawInfo = SceneryDrawInfoTable + 3500; + + for (n = 0; n < NumCullInfos; n++) { + SceneryCullInfo *scenery_cull_info = &SceneryCullInfos[n]; + bool do_precull = true; + + scenery_cull_info->Position = *scenery_cull_info->pView->GetCamera()->GetPosition(); + scenery_cull_info->Direction = *scenery_cull_info->pView->GetCamera()->GetDirection(); + scenery_cull_info->H = scenery_cull_info->pView->H; + + if (PrecullerMode == 0) { + do_precull = false; + } else if (PrecullerMode == 2) { + int time = RealTimeFrames % 0x3D; + if (time < 0xF) { + do_precull = false; + } + } else if (PrecullerMode == 3) { + Camera *camera = scenery_cull_info->pView->GetCamera(); + float speed = bLength(reinterpret_cast(reinterpret_cast(camera) + 0x1E8)); + if (speed < EnablePrecullingSpeed) { + do_precull = false; + } + } + + if (DisablePrecullerCounter > 0 && PrecullerMode != 2) { + do_precull = false; + } + + if (do_precull) { + if (gPrecullerBooBooManager.IsSet(scenery_cull_info->Position)) { + do_precull = false; + } + } + scenery_cull_info->PrecullerSectionNumber = -1; + if (do_precull) { + scenery_cull_info->PrecullerSectionNumber = GetPrecullerSectionNumber(scenery_cull_info->Position.x, scenery_cull_info->Position.y); + } + } + + for (n = 0; n < NumCullInfos; n++) { + SceneryCullInfo *scenery_cull_info = &SceneryCullInfos[n]; + scenery_cull_info->pFirstDrawInfo = pCurrentDrawInfo; + scenery_cull_info->pCurrentDrawInfo = pCurrentDrawInfo; + scenery_cull_info->pTopDrawInfo = pTopDrawInfo; + CullView(scenery_cull_info); + pCurrentDrawInfo = scenery_cull_info->pCurrentDrawInfo; + } + + for (n = 0; n < NumCullInfos; n++) { + } +} + +void RenderVisibleZones(eView *view) { + if (ShowSectionBoarder != 0 && pVisibleZoneBoundaryModel != 0) { + DrivableScenerySection *drivable_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(view->GetCamera()->GetPosition())); + if (drivable_section) { + RenderVisibleSectionBoundary(drivable_section->pBoundary, view); + } + } +} + +void GrandSceneryCullInfo::StuffScenery(eView *view, int stuff_flags) { + unsigned int base_flags = 0; + unsigned int forbidden_flags = 0; + unsigned int required_flags = 0; + + if ((stuff_flags & 1) != 0) { + base_flags = 0x1000; + } + if ((stuff_flags & 0x400) != 0) { + required_flags = 0x100000; + } + if ((stuff_flags & 0x80) != 0) { + base_flags |= 0x100; + } + if ((stuff_flags & 0x10) != 0) { + required_flags = 0x2000; + } else if ((stuff_flags & 8) != 0) { + forbidden_flags = 0x2000; + } + if ((stuff_flags & 0x300) != 0) { + required_flags = 0x10000; + } + if ((stuff_flags & 0x800) != 0) { + forbidden_flags |= 0x400000; + } + if ((stuff_flags & 0x1000) != 0) { + required_flags = 0x1000000; + } + + for (int i = 0; i < NumCullInfos; i++) { + SceneryCullInfo *scenery_cull_info = &SceneryCullInfos[i]; + if (scenery_cull_info->pView != view) { + continue; + } + + for (SceneryDrawInfo *draw_info = scenery_cull_info->pFirstDrawInfo; draw_info < scenery_cull_info->pCurrentDrawInfo; draw_info++) { + unsigned int model_word = reinterpret_cast(draw_info->pModel); + unsigned int model_type = model_word & 3; + unsigned int exclude_flags = draw_info->SceneryInst->ExcludeFlags; + unsigned int render_flags = base_flags; + + pDebugModel = reinterpret_cast(model_word & ~3); + if ((exclude_flags & 0x80) != 0) { + render_flags |= 0x2000; + } + if ((exclude_flags & 0x1000000) != 0) { + render_flags |= 0x100000; + } + if ((exclude_flags & 0x100) != 0) { + render_flags |= 0x20000; + } + if ((exclude_flags & 0x400000) != 0) { + render_flags |= 0x40000; + } + if ((exclude_flags & 0x20000) != 0) { + render_flags |= 0x800000; + } + if ((exclude_flags & 0x80000) != 0) { + render_flags |= 0x400000; + } + if ((stuff_flags & 0x200) != 0) { + if ((exclude_flags & 0x200000) != 0 && ((exclude_flags >> 0x1A) & 1U) == 0) { + render_flags |= 0x10000; + } + if ((exclude_flags & 0x40000000) != 0) { + render_flags |= 0x10000; + } + } + if ((stuff_flags & 0x100) != 0) { + if ((exclude_flags & 0x200000) != 0) { + render_flags |= 0x10000; + } + if ((exclude_flags & 0x40000000) != 0) { + render_flags |= 0x10000; + } + } + if (model_type == 2) { + render_flags |= 4; + } + + bool required_ok = required_flags == 0 || (render_flags & required_flags) != 0; + bool forbidden_ok = forbidden_flags == 0 || (render_flags & forbidden_flags) == 0; + if (required_ok && forbidden_ok) { + bMatrix4 *matrix = draw_info->pMatrix; + if (!matrix) { + reinterpret_cast(view)->Render(pDebugModel, &eMathIdentityMatrix, 0, render_flags, 0); + } else { + reinterpret_cast(view)->Render(pDebugModel, matrix, 0, render_flags, 0); + } + pDebugModel = 0; + } + } + return; + } +} diff --git a/src/Speed/Indep/Src/World/Scenery.hpp b/src/Speed/Indep/Src/World/Scenery.hpp index 6083173f1..d1faa1c97 100644 --- a/src/Speed/Indep/Src/World/Scenery.hpp +++ b/src/Speed/Indep/Src/World/Scenery.hpp @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/Src/World/WeatherMan.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" @@ -22,6 +23,11 @@ struct SceneryBoundingBox { // total size: 0x18 float BBoxMin[3]; // offset 0x0, size 0xC float BBoxMax[3]; // offset 0xC, size 0xC + + void GetBBox(bVector3 *bbox_min, bVector3 *bbox_max) { + bFill(bbox_min, BBoxMin[0], BBoxMin[1], BBoxMin[2]); + bFill(bbox_max, BBoxMax[0], BBoxMax[1], BBoxMax[2]); + } }; struct SceneryInstance : public SceneryBoundingBox { @@ -32,13 +38,53 @@ struct SceneryInstance : public SceneryBoundingBox { float Position[3]; // offset 0x20, size 0xC short Rotation[9]; // offset 0x2C, size 0x12 short SceneryInfoNumber; // offset 0x3E, size 0x2 + + void GetRotation(bMatrix4 *matrix) { + const float rotation_conversion = 0.00012207031f; + float x = static_cast(Rotation[0]) * rotation_conversion; + float y = static_cast(Rotation[1]) * rotation_conversion; + float z = static_cast(Rotation[2]) * rotation_conversion; + bFill(&matrix->v0, x, y, z, 0.0f); + x = static_cast(Rotation[3]) * rotation_conversion; + y = static_cast(Rotation[4]) * rotation_conversion; + z = static_cast(Rotation[5]) * rotation_conversion; + bFill(&matrix->v1, x, y, z, 0.0f); + x = static_cast(Rotation[6]) * rotation_conversion; + y = static_cast(Rotation[7]) * rotation_conversion; + z = static_cast(Rotation[8]) * rotation_conversion; + bFill(&matrix->v2, x, y, z, 0.0f); + bFill(&matrix->v3, 0.0f, 0.0f, 0.0f, 1.0f); + } + + void GetPosition(bVector4 *position) { + bFill(position, Position[0], Position[1], Position[2], 1.0f); + } + + bVector3 *GetPosition() { + return reinterpret_cast(Position); + } + + void SetMatrix(const bMatrix4 *matrix) { + const float rotation_conversion = 8192.0f; + Rotation[0] = static_cast(matrix->v0.x * rotation_conversion); + Rotation[1] = static_cast(matrix->v0.y * rotation_conversion); + Rotation[2] = static_cast(matrix->v0.z * rotation_conversion); + Rotation[3] = static_cast(matrix->v1.x * rotation_conversion); + Rotation[4] = static_cast(matrix->v1.y * rotation_conversion); + Rotation[5] = static_cast(matrix->v1.z * rotation_conversion); + Rotation[6] = static_cast(matrix->v2.x * rotation_conversion); + Rotation[7] = static_cast(matrix->v2.y * rotation_conversion); + Rotation[8] = static_cast(matrix->v2.z * rotation_conversion); + Position[0] = matrix->v3.x; + Position[1] = matrix->v3.y; + Position[2] = matrix->v3.z; + } }; struct SceneryDrawInfo { // total size: 0xC - eModel *pModel; // offset 0x0, size 0x4 - bMatrix4 *pMatrix; // offset 0x4, size 0x4 - char unk08[4]; + eModel *pModel; // offset 0x0, size 0x4 + bMatrix4 *pMatrix; // offset 0x4, size 0x4 SceneryInstance *SceneryInst; // offset 0x8, size 0x4 }; @@ -57,6 +103,129 @@ struct SceneryCullInfo { int PrecullerSectionNumber; // offset 0xB8, size 0x4 }; +// total size: 0x8 +struct ModelHeirarchy { + enum Flags { + F_INTERNAL = 1, + }; + // total size: 0x10 + struct Node { + UCrc32 mNodeName; // offset 0x0, size 0x4 + unsigned int mModelHash; // offset 0x4, size 0x4 + eModel *mModel; // offset 0x8, size 0x4 + unsigned char mFlags; // offset 0xC, size 0x1 + unsigned char mParent; // offset 0xD, size 0x1 + unsigned char mNumChildren; // offset 0xE, size 0x1 + unsigned char mChildIndex; // offset 0xF, size 0x1 + }; + + const Node *GetNodes() const {} + + Node *GetNodes() {} + + unsigned int GetSize() const {} + + unsigned int mNameHash; // offset 0x0, size 0x4 + unsigned char mNumNodes; // offset 0x4, size 0x1 + unsigned char mFlags; // offset 0x5, size 0x1 + unsigned short pad; // offset 0x6, size 0x2 +}; + +// total size: 0x48 +struct SceneryInfo { + // Members + char DebugName[24]; // offset 0x0, size 0x18 + unsigned int NameHash[4]; // offset 0x18, size 0x10 + eModel *pModel[4]; // offset 0x28, size 0x10 + float Radius; // offset 0x38, size 0x4 + unsigned int MeshChecksum; // offset 0x3C, size 0x4 + unsigned int mHeirarchyNameHash; // offset 0x40, size 0x4 + ModelHeirarchy *mHeirarchy; // offset 0x44, size 0x4 +}; + +class tPrecullerInfo { + public: + bool IsVisible(int preculler_section_number) { + return (VisibilityBits[preculler_section_number >> 3] & (1 << (preculler_section_number & 7))) != 0; + } + + bool IsNotVisible(int preculler_section_number) { + return !IsVisible(preculler_section_number); + } + + unsigned char *GetBits() { + return VisibilityBits; + } + + private: + unsigned char VisibilityBits[0x80]; +}; + +// total size: 0x24 +struct SceneryTreeNode : public SceneryBoundingBox { + short NumChildren; // offset 0x18, size 0x2 + short ChildCodes[5]; // offset 0x1A, size 0xA +}; + +// TODO +class ScenerySectionHeader : public bTNode { + public: + void DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state); + + int IsVisible(SceneryCullInfo *scenery_cull_info); + + void CullBruteForce(SceneryCullInfo *scenery_cull_info); + + void TreeCull(SceneryCullInfo *scenery_cull_info); + + void CullNodeRecursive(SceneryTreeNode *node, SceneryCullInfo *scenery_cull_info, unsigned int visibility_state); + + SceneryInstance *GetSceneryInstance(int scenery_instance_number) { + return &pSceneryInstance[scenery_instance_number]; + } + + int GetSectionNumber() { + return this->SectionNumber; + } + + tPrecullerInfo *GetPrecullerInfo(int preculler_info_index) { + return &PrecullerInfoTable[preculler_info_index]; + } + + static float mLodLevelDistance[3]; // size: 0xC, address: 0xFFFFFFFF + + int ChunksLoaded; // offset 0x8, size 0x4 + int SectionNumber; // offset 0xC, size 0x4 + int NumPolygonsInMemory; // offset 0x10, size 0x4 + int NumPolygonsInWorld; // offset 0x14, size 0x4 + SceneryInfo *pSceneryInfo; // offset 0x18, size 0x4 + int NumSceneryInfo; // offset 0x1C, size 0x4 + SceneryInstance *pSceneryInstance; // offset 0x20, size 0x4 + int NumSceneryInstances; // offset 0x24, size 0x4 + SceneryTreeNode *SceneryTreeNodeTable; // offset 0x28, size 0x4 + int NumSceneryTreeNodes; // offset 0x2C, size 0x4 + tPrecullerInfo *PrecullerInfoTable; // offset 0x30, size 0x4 + int NumPrecullerInfos; // offset 0x34, size 0x4 + int ViewsVisibleThisFrame; // offset 0x38, size 0x4 +}; + +struct SceneryOverrideInfo { + void EndianSwap() { + bPlatEndianSwap(&SectionNumber); + bPlatEndianSwap(&InstanceNumber); + bPlatEndianSwap(&ExcludeFlags); + } + + void AssignOverrides(); + void AssignOverrides(ScenerySectionHeader *section_header); + + short SectionNumber; // offset 0x0, size 0x2 + short InstanceNumber; // offset 0x2, size 0x2 + unsigned short ExcludeFlags; // offset 0x4, size 0x2 +}; + +extern SceneryOverrideInfo *SceneryOverrideInfoTable; + // total size: 0x8014 struct SceneryGroup : public bTNode { // SceneryGroup(unsigned int name_hash) {} @@ -67,7 +236,9 @@ struct SceneryGroup : public bTNode { // int GetOverrideInfoNumber(int index) {} - // struct SceneryOverrideInfo *GetOverrideInfo(int index) {} + SceneryOverrideInfo *GetOverrideInfo(int index) { + return &SceneryOverrideInfoTable[OverrideInfoNumbers[index]]; + } // void EndianSwap() {} @@ -93,6 +264,6 @@ void CloseVisibleZones(); void ServicePreculler(); void LoadPrecullerBooBooScripts(); void EnableSceneryGroup(unsigned int group_name_hash, bool flip_artwork); -SceneryGroup *FindSceneryGroup(unsigned int name_hash); // TODO remove "class" +SceneryGroup *FindSceneryGroup(unsigned int name_hash); #endif diff --git a/src/Speed/Indep/Src/World/ScreenEffects.cpp b/src/Speed/Indep/Src/World/ScreenEffects.cpp index e69de29bb..d7576847c 100644 --- a/src/Speed/Indep/Src/World/ScreenEffects.cpp +++ b/src/Speed/Indep/Src/World/ScreenEffects.cpp @@ -0,0 +1,497 @@ +#include "ScreenEffects.hpp" + +#include "Scenery.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/EcstasyE.hpp" +#include "Speed/Indep/Src/Ecstasy/eLight.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/World/TrackPath.hpp" +#include "Speed/Indep/Src/World/Rain.hpp" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/Src/World/WeatherMan.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +static unsigned int AccumulationBufferNeedsFlush = 0; +ScreenEffectPaletteDef SE_PaletteFile[EFX_NUMBER]; + +class eViewRenderShim : public eView { + public: + void Render(eModel *model, bMatrix4 *matrix, eLightContext *light_context, unsigned int a4, unsigned int a5, unsigned int a6); +}; + +extern eModel *pVisibleZoneBoundaryModel; +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern float GlareFalloff; +extern float GlareFallon; +extern float TUNHEIGHT; +extern int debugflash; +extern TrackPathZone *zoneB[2]; + +static inline UMath::Vector3 &bConvertToBond(UMath::Vector3 &dest, const bVector3 &v) { + bConvertToBond(*reinterpret_cast(&dest), v); + return dest; +} + +static int __tmp_14_27615; +static bVector3 lcamPosInside_27614[2]; +static float dataBackup_27616[2][12]; +static GenericRegion *regionB_27617[2]; +static unsigned int ticS_27592; + +class WWorldPosTopologyShim : public WWorldPos { + public: + WWorldPosTopologyShim(float yOffset) + : WWorldPos(yOffset) { + fFace.fPt0 = UMath::Vector3::kZero; + fFace.fPt1 = UMath::Vector3::kZero; + fFace.fPt2 = UMath::Vector3::kZero; + fFace.fSurface.fSurface = 0; + fFace.fSurface.fFlags = 0; + } +}; + +void InitScreenEFX() {} + +enum TunnelBloomDataIndex { + kTunnelPoint0X = 0, + kTunnelPoint0Y = 1, + kTunnelPoint0Z = 2, + kTunnelPoint1X = 3, + kTunnelPoint1Y = 4, + kTunnelPoint1Z = 5, + kTunnelPoint2X = 6, + kTunnelPoint2Y = 7, + kTunnelPoint2Z = 8, + kTunnelPoint3X = 9, + kTunnelPoint3Y = 10, + kTunnelPoint3Z = 11, +}; + +ScreenEffectDB::ScreenEffectDB() { + SE_time = 0.0f; + for (int i = 0; i < SE_NUM_TYPES; i++) { + SE_inf[i].active = 0; + SE_data[i].r = 0.0f; + SE_data[i].g = 0.0f; + SE_data[i].b = 0.0f; + SE_data[i].a = 0.0f; + for (int j = 0; j < 14; j++) { + SE_data[i].data[j] = 0.0f; + } + SE_data[i].intensity = 0.0f; + SE_data[i].UpdateFnc = 0; + numType[i] = 0; + } + InitScreenEFX(); +} +void TickSFX() { + if (TheGameFlowManager.IsInGame()) { + if (ticS_27592 != eFrameCounter - 1) { + UpdateAllScreenEFX(); + } + ticS_27592 = eFrameCounter; + } +} + +void ScreenEffectDB::Update(float deltatime) { + SE_time += deltatime; + + for (int i = 0; i < SE_NUM_TYPES; i++) { + if (SE_inf[i].active == 1) { + SE_inf[i].frameNum += 1; + ScreenEffectControl controller = SE_inf[i].Controller; + if (controller == SEC_FRAME || controller == SEC_FUNCTION) { + SE_inf[i].active = 0; + numType[i] = 0; + } + } + } +} + +float TopologyCoordinate::GetElevation(const bVector3 *position, enum TerrainType *type, bVector3 *normal, bool *point_valid) { + UMath::Vector3 bond_pos; + UMath::Vector4 dummy_normal; + + (void)type; + (void)normal; + + bConvertToBond(bond_pos, *position); + WWorldPosTopologyShim world_pos(0.025f); + world_pos.Update(bond_pos, dummy_normal, true, 0, true); + if (point_valid) { + *point_valid = world_pos.OnValidFace(); + } + if (world_pos.OnValidFace()) { + return world_pos.HeightAtPoint(bond_pos); + } + return position->z; +} + +void ScreenEffectDB::AddScreenEffect(ScreenEffectType type, float intensity, float r, float g, float b) { + ScreenEffectDef info; + + info.intensity = intensity; + info.r = r; + info.g = g; + info.b = b; + info.UpdateFnc = 0; + AddScreenEffect(type, &info, 1, SEC_FRAME); +} + +void ScreenEffectDB::AddScreenEffect(ScreenEffectType type, ScreenEffectDef *info, unsigned int lock, + ScreenEffectControl controller) { + if (lock != 0) { + if (info) { + SE_data[type] = *info; + } + numType[type] = 1; + } else { + float influence; + float invFluence; + + numType[type] += 1; + influence = static_cast(numType[type]) / static_cast(numType[type] + 1); + invFluence = 1.0f - influence; + + SE_data[type].r = influence * SE_data[type].r + invFluence * info->r; + SE_data[type].g = influence * SE_data[type].g + invFluence * info->g; + SE_data[type].b = influence * SE_data[type].b + invFluence * info->b; + SE_data[type].a = influence * SE_data[type].a + invFluence * info->a; + SE_data[type].intensity = influence * SE_data[type].intensity + invFluence * info->intensity; + } + + SE_inf[type].active = 1; + if (SE_data[type].UpdateFnc) { + SE_data[type].UpdateFnc(type, this); + } else { + SetController(type, controller); + } + + if (SE_data[type].intensity < 0.01f) { + SE_inf[type].active = 0; + } +} + +void ScreenEffectDB::AddPaletteEffect(ScreenEffectPaletteDef *palette) { + for (int i = 0; i < palette->NumEffects; i++) { + AddScreenEffect(palette->SE_type[i], &palette->SE_Def[i], 1, palette->SE_Controller[i]); + } +} + +void ScreenEffectDB::AddPaletteEffect(ScreenEffectPalette palette) { + AddPaletteEffect(&SE_PaletteFile[palette]); +} + +void RenderVisibleSectionBoundary(VisibleSectionBoundary *boundary, eView *view) { + if (boundary->NumPoints <= 0) { + return; + } + + float perimeter; + { + int n; + + for (n = 0; n < boundary->GetNumPoints(); n++) { + bVector2 *v1 = boundary->GetPoint(n); + bVector2 *v2 = boundary->GetPoint((n + 1) % boundary->GetNumPoints()); + float x = v1->x - v2->x; + float y = v1->y - v2->y; + perimeter = bSqrt(x * x + y * y); + } + } + + bVector3 position; + TopologyCoordinate topology_coordinate; + float pos = static_cast((static_cast(WorldTimer.GetSeconds() * 262144.0f) & 0xffff)) * 6.103515625e-05f; + int point_number; + + for (point_number = 0; point_number < boundary->GetNumPoints(); point_number++) { + bVector2 normal = *boundary->GetPoint((point_number + 1) % boundary->GetNumPoints()) - *boundary->GetPoint(point_number); + float length = bLength(&normal); + + bNormalize(&normal, &normal); + if (pos < length) { + do { + bScaleAdd(reinterpret_cast(&position), boundary->GetPoint(point_number), &normal, pos); + + if (topology_coordinate.HasTopology(reinterpret_cast(&position))) { + position.z = 9999.0f; + position.z = topology_coordinate.GetElevation(&position, 0, 0, 0); + int pixel_size = view->GetPixelSize(&position, 1.0f); + if (pixel_size > 0) { + unsigned char *matrix_memory = CurrentBufferPos; + unsigned char *next_buffer_pos = matrix_memory + sizeof(bMatrix4); + if (next_buffer_pos >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += sizeof(bMatrix4); + matrix_memory = 0; + } else { + CurrentBufferPos = next_buffer_pos; + } + + if (matrix_memory) { + bMatrix4 *matrix = reinterpret_cast(matrix_memory); + bIdentity(matrix); + bCopy(&matrix->v3, &position, 1.0f); + reinterpret_cast(view)->Render(pVisibleZoneBoundaryModel, matrix, 0, 0, 0); + } + } + } + + pos += 4.0f; + } while (pos < length); + } + + pos -= length; + } +} + +void DoTunnelBloom(eView *view) { + int vIndex = 1; + if (view->GetID() == 1) { + vIndex = 0; + } + + if (!view->IsActive()) { + return; + } + + CameraMover *camera_mover = view->GetCameraMover(); + if (!camera_mover) { + return; + } + + CameraAnchor *camera_anchor = camera_mover->GetAnchor(); + if (!camera_anchor) { + return; + } + + bVector3 *my_car_pos = camera_anchor->GetGeometryPosition(); + Camera *view_camera = view->GetCamera(); + bVector3 *camera_position = view_camera->GetPosition(); + bVector3 *camera_direction = view_camera->GetDirection(); + bVector2 twoDpos(camera_position->x, camera_position->y); + + if (!__tmp_14_27615) { + int i = 1; + do { + i -= 1; + } while (i + 1 != 0); + __tmp_14_27615 = 1; + } + + TrackPathZone *zone = 0; + bVector3 endVector; + bVector3 posScreen; + TrackPathZone *zoneBP = zoneB[vIndex]; + if (zoneBP && zoneBP->IsPointInside(&twoDpos)) { + zone = zoneB[vIndex]; + } else { + zone = TheTrackPathManager.FindZone(&twoDpos, TRACK_PATH_ZONE_TUNNEL, 0); + } + + if (zone && zone->GetElevation() > my_car_pos->z) { + lcamPosInside_27614[vIndex] = *camera_position; + float angleCos = 0.0f; + GenericRegion *end_tunnel = GetClosestRegionInView(view, &endVector, &angleCos); + if (!end_tunnel) { + return; + } + + ScreenEffectDef SE_def; + bVector2 endP(endVector.x, endVector.y); + bVector2 p0; + bVector2 p1; + float len = zone->GetSegmentNextTo(&endP, &p0, &p1); + if (len == -1.0f || len >= 40.0f) { + return; + } + + bVector3 p3(endVector); + UMath::Vector3 usPoint; + bConvertToBond(usPoint, p3); + float height = 0.0f; + WCollisionMgr(0, 3).GetWorldHeightAtPointRigorous(usPoint, height, 0); + + dataBackup_27616[vIndex][kTunnelPoint0X] = p0.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint0Y] = p0.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint0Z] = height + camera_direction->z; + dataBackup_27616[vIndex][kTunnelPoint1X] = p1.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint1Y] = p1.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint1Z] = height + camera_direction->z; + dataBackup_27616[vIndex][kTunnelPoint2X] = p0.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint2Y] = p0.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint2Z] = height + TUNHEIGHT + camera_direction->z; + dataBackup_27616[vIndex][kTunnelPoint3X] = p1.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint3Y] = p1.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint3Z] = height + TUNHEIGHT + camera_direction->z; + + SE_def.r = 128.0f; + SE_def.g = 128.0f; + SE_def.b = 128.0f; + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.data[0] = dataBackup_27616[vIndex][kTunnelPoint0X]; + SE_def.data[1] = dataBackup_27616[vIndex][kTunnelPoint0Y]; + SE_def.data[2] = dataBackup_27616[vIndex][kTunnelPoint0Z]; + SE_def.data[3] = dataBackup_27616[vIndex][kTunnelPoint1X]; + SE_def.data[4] = dataBackup_27616[vIndex][kTunnelPoint1Y]; + SE_def.data[5] = dataBackup_27616[vIndex][kTunnelPoint1Z]; + SE_def.data[6] = dataBackup_27616[vIndex][kTunnelPoint2X]; + SE_def.data[7] = dataBackup_27616[vIndex][kTunnelPoint2Y]; + SE_def.data[8] = dataBackup_27616[vIndex][kTunnelPoint2Z]; + SE_def.data[9] = dataBackup_27616[vIndex][kTunnelPoint3X]; + SE_def.data[10] = dataBackup_27616[vIndex][kTunnelPoint3Y]; + SE_def.data[11] = dataBackup_27616[vIndex][kTunnelPoint3Z]; + + if (regionB_27617[vIndex] != end_tunnel) { + view->ScreenEffects->SetDATA(SE_GLARE, 0.0f, 1); + } + regionB_27617[vIndex] = end_tunnel; + if (zoneB[vIndex] != zone) { + view->ScreenEffects->SetDATA(SE_GLARE, 0.0f, 1); + } + zoneB[vIndex] = zone; + + bVector2 r = p0 - twoDpos; + bVector2 v(p1.y - p0.y, p0.x - p1.x); + bNormalize(&v, &v); + float dir_dot = bAbs(bDot(&v, &r)); + if (dir_dot < 17.0f) { + SE_def.intensity = view->ScreenEffects->GetDATA(SE_GLARE, 1) * 0.05882353f * dir_dot; + } else { + SE_def.intensity = 1.0f; + if (view->ScreenEffects->GetDATA(SE_GLARE, 1) < 1.0f) { + SE_def.intensity = view->ScreenEffects->GetDATA(SE_GLARE, 1) + GlareFallon; + } + } + + view->ScreenEffects->SetDATA(SE_GLARE, SE_def.intensity, 1); + view->ScreenEffects->AddScreenEffect(SE_GLARE, &SE_def, 1, SEC_FRAME); + AccumulationBufferNeedsFlush = 1; + + if (view->Precipitation && 0.0f < view->Precipitation->GetRainIntensity()) { + view->Precipitation->IsValidRainCurtainPos = CT_OVERIDE; + view->Precipitation->AttachRainCurtain( + SE_def.data[6], + SE_def.data[7], + SE_def.data[8], + SE_def.data[9], + SE_def.data[10], + SE_def.data[11], + SE_def.data[0], + SE_def.data[1], + SE_def.data[2], + SE_def.data[3], + SE_def.data[4], + SE_def.data[5] + ); + } + return; + } + + if (0.0f < view->ScreenEffects->GetIntensity(SE_GLARE)) { + ScreenEffectDef SE_def; + bVector3 midpoint( + dataBackup_27616[vIndex][kTunnelPoint0X], + dataBackup_27616[vIndex][kTunnelPoint0Y], + dataBackup_27616[vIndex][kTunnelPoint0Z] + ); + bVector3 ToGlare; + float BaseGlare = view->ScreenEffects->GetIntensity(SE_GLARE) - GlareFalloff; + + midpoint += bVector3( + dataBackup_27616[vIndex][kTunnelPoint1X], + dataBackup_27616[vIndex][kTunnelPoint1Y], + dataBackup_27616[vIndex][kTunnelPoint1Z] + ); + midpoint *= 0.5f; + + SE_def.r = 128.0f; + SE_def.g = 128.0f; + SE_def.b = 128.0f; + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.intensity = BaseGlare; + ToGlare = *camera_position - lcamPosInside_27614[vIndex]; + ToGlare += *camera_direction; + + if (0.0f < BaseGlare) { + SE_def.data[0] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint0X]; + SE_def.data[1] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint0Y]; + SE_def.data[2] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint0Z]; + SE_def.data[3] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint1X]; + SE_def.data[4] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint1Y]; + SE_def.data[5] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint1Z]; + SE_def.data[6] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint2X]; + SE_def.data[7] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint2Y]; + SE_def.data[8] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint2Z]; + SE_def.data[9] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint3X]; + SE_def.data[10] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint3Y]; + SE_def.data[11] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint3Z]; + view->ScreenEffects->AddScreenEffect(SE_GLARE, &SE_def, 1, SEC_FRAME); + AccumulationBufferNeedsFlush = 1; + } + } +} + +void DoTinting(eView *view) { + ScreenEffectDef SE_def; + unsigned int r; + unsigned int g; + unsigned int b; + float intense; + + if (IsRainDisabled()) { + return; + } + + if (view->Precipitation) { + intense = view->Precipitation->GetCloudIntensity(); + } else { + intense = 0.0f; + } + + if (0.0f < intense) { + if (view->Precipitation) { + view->Precipitation->GetPrecipFogColour(&r, &g, &b); + } + SE_def.r = static_cast(r); + SE_def.g = static_cast(g); + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.intensity = intense; + SE_def.b = static_cast(b); + view->ScreenEffects->AddScreenEffect(SE_TINT, &SE_def, 1, SEC_FRAME); + } +} + +void UpdateAllScreenEFX() { + for (int i = 1; i <= 2; i++) { + eView *view = eGetView(i, false); + if (view->IsActive()) { + eGetView(i, false)->ScreenEffects->Update(0.033333335f); + if (debugflash != 0) { + debugflash = 0; + eGetView(i, false)->ScreenEffects->AddPaletteEffect(EFX_CAMERA_FLASH); + } + } + } +} + +void FlushAccumulationBuffer() { + AccumulationBufferNeedsFlush = 1; +} + +void AccumulationBufferFlushed() { + AccumulationBufferNeedsFlush = 0; +} + +unsigned int QueryFlushAccumulationBuffer() { + return AccumulationBufferNeedsFlush; +} diff --git a/src/Speed/Indep/Src/World/ScreenEffects.hpp b/src/Speed/Indep/Src/World/ScreenEffects.hpp index c4724a7c6..307e3aab3 100644 --- a/src/Speed/Indep/Src/World/ScreenEffects.hpp +++ b/src/Speed/Indep/Src/World/ScreenEffects.hpp @@ -5,10 +5,25 @@ #pragma once #endif +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" + +inline float ScreenEffectDB::GetIntensity(ScreenEffectType type) { + return SE_data[type].intensity; +} + +inline float ScreenEffectDB::GetDATA(ScreenEffectType type, int index) { + return SE_data[type].data[index]; +} + +inline void ScreenEffectDB::SetDATA(ScreenEffectType type, float data, int index) { + SE_data[type].data[index] = data; +} + void TickSFX(); void DoTunnelBloom(struct eView *view /* r25 */); void DoTinting(struct eView *view /* r31 */); void UpdateAllScreenEFX(); +void FlushAccumulationBuffer(); void AccumulationBufferFlushed(); unsigned int QueryFlushAccumulationBuffer(); diff --git a/src/Speed/Indep/Src/World/Skids.cpp b/src/Speed/Indep/Src/World/Skids.cpp index e69de29bb..37a2f6692 100644 --- a/src/Speed/Indep/Src/World/Skids.cpp +++ b/src/Speed/Indep/Src/World/Skids.cpp @@ -0,0 +1,348 @@ +#include "Skids.hpp" + +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bVector.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void bInitializeBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *point); +void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *point, float extra_width); +void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *bbox2_min, const bVector3 *bbox2_max); +int bIsSlotPoolFull(SlotPool *slot_pool); +extern bVector3 ZeroVector; + +static const int kNumSkidSegments_Skids = 8; +static const int kNumSkidTextures_Skids = 29; +static const float kSkidSegmentScale_Skids = 64.0f; +static const float kInverseSkidSegmentScale_Skids = 1.0f / 64.0f; +static const float kSkidDirectionBreakThreshold_Skids = 0.2f; +static const float kSkidDirectionMergeThreshold_Skids = 0.002f; +static const float kSkidLengthMergeThreshold_Skids = 0.25f; +static const float kSkidLengthSplitThreshold_Skids = 3.0f; +static const float kSkidIntensityScale_Skids = 255.0f; +static const unsigned int kSkidColour_Skids = 0x80808080; + +class eViewSkidRenderShim : public eView { + public: + void Render(ePoly *poly, TextureInfo *texture_info, bMatrix4 *matrix, int flags, float z_bias); +}; + +void SkidSegment::SetPoints(bVector3 *position, bVector3 *delta_position) { + const float scale_factor = kSkidSegmentScale_Skids; + float x = position->x; + float y = position->y; + float z = position->z; + int dx = static_cast(delta_position->x * scale_factor); + int dy = static_cast(delta_position->y * scale_factor); + int dz = static_cast(delta_position->z * scale_factor); + + Position[0] = x; + Position[1] = y; + Position[2] = z; + DeltaPosition[0] = static_cast(dx); + DeltaPosition[1] = static_cast(dy); + DeltaPosition[2] = static_cast(dz); +} + +SlotPool *SkidSetSlotPool = 0; +int PlotSkidsInCaffeine = 0; +int PlotSkidPointsInCaffeine = 0; +bTList SkidSetList; +TextureInfo *SkidTextureInfo[kNumSkidTextures_Skids]; + +void SkidSegment::GetPoints(bVector3 *position, bVector3 *delta_position) { + const float scale_factor = kInverseSkidSegmentScale_Skids; + float x; + float y; + float z; + float dx; + float dy; + float dz; + + dx = static_cast(DeltaPosition[0]) * scale_factor; + dy = static_cast(DeltaPosition[1]) * scale_factor; + y = Position[1]; + dz = static_cast(DeltaPosition[2]) * scale_factor; + z = Position[2]; + x = Position[0]; + + position->x = x; + position->y = y; + position->z = z; + + if (delta_position) { + delta_position->x = dx; + delta_position->y = dy; + delta_position->z = dz; + } +} + +void SkidSegment::GetEndPoints(bVector3 *left_point, bVector3 *right_point) { + const float scale_factor = kInverseSkidSegmentScale_Skids; + float x; + float y; + float z; + float dx; + float dy; + float dz; + + x = Position[0]; + dx = static_cast(DeltaPosition[0]) * scale_factor; + y = Position[1]; + z = Position[2]; + dy = static_cast(DeltaPosition[1]) * scale_factor; + dz = static_cast(DeltaPosition[2]) * scale_factor; + left_point->x = x + dx; + left_point->y = y + dy; + left_point->z = z + dz; + right_point->x = x - dx; + right_point->y = y - dy; + right_point->z = z - dz; +} + +SkidSet::SkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity) { + TheTerrainType = terrain_type; + NumSkidSegments = 0; + pSkidMaker = skid_maker; + Position = *position; + bInitializeBoundingBox(&BBoxMin, &BBoxMax, position); + BBoxCentre = *position; + + pClan = GetClan(position); + pClanNode = pClan->SkidSetList.AddTail(this); + + AddSegment(position, delta_position, false, intensity); +} + +SkidSet::~SkidSet() { + if (pSkidMaker) { + pSkidMaker->MakeNoSkid(); + } + + pClan->SkidSetList.Remove(pClanNode); +} + +int SkidSet::AddSegment(bVector3 *position, bVector3 *delta_position, bool skid_is_flaming, float intensity) { + (void)skid_is_flaming; + + bVector3 new_segment_forward; + float length = 0.0f; + if (NumSkidSegments > 0) { + bVector3 new_segment_normal = *position - *SkidSegments[NumSkidSegments - 1].GetPosition(); + bVector3 new_segment_delta(new_segment_normal); + length = bLength(&new_segment_delta); + bNormalize(&new_segment_forward, &new_segment_delta); + } + + int expand_last_skid_segment = 0; + if (NumSkidSegments > 1) { + float error = 1.0f - bDot(&new_segment_forward, &LastNormal); + float new_segment_length = LastSegmentLength + length; + if (error > kSkidDirectionBreakThreshold_Skids) { + FinishedAddingSkids(); + return 0; + } + + if (new_segment_length < kSkidLengthMergeThreshold_Skids) { + expand_last_skid_segment = 1; + } + if (error < kSkidDirectionMergeThreshold_Skids) { + expand_last_skid_segment = 1; + } + if (new_segment_length > kSkidLengthSplitThreshold_Skids) { + expand_last_skid_segment = 0; + } + } + + SkidSegment *skid_segment; + if (expand_last_skid_segment) { + skid_segment = &SkidSegments[NumSkidSegments - 1]; + LastSegmentLength += length; + } else if (NumSkidSegments == kNumSkidSegments_Skids) { + return 1; + } else { + skid_segment = &SkidSegments[NumSkidSegments]; + NumSkidSegments += 1; + if (NumSkidSegments > 1) { + LastNormal = new_segment_forward; + } + LastSegmentLength = length; + } + + skid_segment->SetPoints(position, delta_position); + skid_segment->SetIntensity(static_cast(intensity * kSkidIntensityScale_Skids)); + + bExpandBoundingBox(&BBoxMin, &BBoxMax, position, length); + pClan->ExpandBoundingBox(&BBoxMin, &BBoxMax); + + bAdd(&BBoxCentre, &BBoxMin, &BBoxMax); + bScale(&BBoxCentre, &BBoxCentre, 0.5f); + return 0; +} + +void SkidSet::FinishedAddingSkids() { + if (pSkidMaker) { + pSkidMaker->pSkidSet = 0; + pSkidMaker = 0; + } +} + +void SkidSet::Render(eView *view, unsigned char intensityReduction) { + if (!SkidTextureInfo[TheTerrainType]) { + return; + } + + bMatrix4 *identity_matrix = eGetIdentityMatrix(); + ePoly poly; + float extra_height = 0.05f; + + for (int n = 0; n < NumSkidSegments - 1; n++) { + SkidSegment *skid_segment = &SkidSegments[n]; + SkidSegment *next_skid_segment = &SkidSegments[n + 1]; + unsigned char alpha0; + unsigned char alpha1; + + skid_segment->GetEndPoints(&poly.Vertices[0], &poly.Vertices[3]); + next_skid_segment->GetEndPoints(&poly.Vertices[1], &poly.Vertices[2]); + poly.Vertices[0].z += extra_height; + poly.Vertices[1].z += extra_height; + poly.Vertices[2].z += extra_height; + poly.Vertices[3].z += extra_height; + + alpha0 = skid_segment->GetIntensity(); + alpha1 = next_skid_segment->GetIntensity(); + if (intensityReduction > alpha0) { + alpha0 = 0; + } else { + alpha0 -= intensityReduction; + } + if (intensityReduction > alpha1) { + alpha1 = 0; + } else { + alpha1 -= intensityReduction; + } + + *reinterpret_cast(&poly.Colours[0][0]) = kSkidColour_Skids; + *reinterpret_cast(&poly.Colours[1][0]) = kSkidColour_Skids; + *reinterpret_cast(&poly.Colours[2][0]) = kSkidColour_Skids; + *reinterpret_cast(&poly.Colours[3][0]) = kSkidColour_Skids; + poly.Colours[0][3] = alpha0; + poly.Colours[1][3] = alpha1; + poly.Colours[2][3] = alpha1; + poly.Colours[3][3] = alpha0; + reinterpret_cast(view)->Render(&poly, SkidTextureInfo[TheTerrainType], identity_matrix, 0, + 0.05f); + } +} + +SkidSet *CreateNewSkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity) { + if (bIsSlotPoolFull(SkidSetSlotPool)) { + SkidSet *oldest_skid_set = static_cast(SkidSetList.GetTail()->Remove()); + if (oldest_skid_set) { + delete oldest_skid_set; + } + } + + SkidSet *skid_set = new SkidSet(skid_maker, position, delta_position, terrain_type, intensity); + SkidSetList.AddHead(skid_set); + return skid_set; +} + +void SkidMaker::MakeSkid(Car *pCar, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity) { + bool make_flaming_skids = false; + if (pCar) { + float distance_from_car = bDistBetween(0, position); + if (distance_from_car > 4.0f) { + return; + } + } + + if (!pSkidSet) { + pSkidSet = CreateNewSkidSet(this, position, delta_position, terrain_type, intensity); + } else if (pSkidSet->GetTerrainType() != terrain_type || + pSkidSet->AddSegment(position, delta_position, make_flaming_skids, intensity) != 0) { + bVector3 last_position; + bVector3 last_delta_position; + float last_intensity; + + pSkidSet->GetLastPoints(&last_position, &last_delta_position); + last_intensity = pSkidSet->GetLastIntensity(); + pSkidSet->FinishedAddingSkids(); + SkidSet *new_skid_set = CreateNewSkidSet(this, &last_position, &last_delta_position, terrain_type, last_intensity); + new_skid_set->AddSegment(position, delta_position, make_flaming_skids, intensity); + pSkidSet = new_skid_set; + } +} + +void SkidMaker::MakeNoSkid() { + if (pSkidSet) { + pSkidSet->FinishedAddingSkids(); + } +} + +void InitSkids(int max_skids) { + if (!SkidSetSlotPool) { + SkidSetSlotPool = bNewSlotPool(0xF0, max_skids, "SkidSetSlotPool", GetVirtualMemoryAllocParams()); + SkidSetSlotPool->Flags = static_cast(SkidSetSlotPool->Flags & ~1); + } + + for (int i = 0; i < kNumSkidTextures_Skids; i++) { + SkidTextureInfo[i] = 0; + SkidTextureInfo[i] = GetTextureInfo(bStringHash("SKID_ROAD"), 1, 0); + } + + PlotSkidsInCaffeine = 0; + PlotSkidPointsInCaffeine = 0; +} + +void CloseSkids() { + if (SkidSetSlotPool) { + bDeleteSlotPool(SkidSetSlotPool); + SkidSetSlotPool = 0; + } + + for (int n = 0; n < kNumSkidTextures_Skids; n++) { + SkidTextureInfo[n] = 0; + } +} + +void DeleteThisSkid(SkidSet *skid_set) { + SkidSetList.Remove(skid_set); + if (skid_set) { + delete skid_set; + } +} + +void DeleteAllSkids() { + while (!SkidSetList.IsEmpty()) { + delete static_cast(SkidSetList.GetTail()->Remove()); + } +} + +void RenderSkids(eView *view, Clan *clan) { + ProfileNode profile_node("TODO", 0); + + for (bPNode *p = clan->SkidSetList.GetHead(); p != clan->SkidSetList.EndOfList(); p = p->GetNext()) { + SkidSet *skid_set = reinterpret_cast(p->GetObject()); + eVisibleState visibility = view->GetVisibleState(skid_set->GetBBoxMin(), skid_set->GetBBoxMax(), 0); + if (visibility != EVISIBLESTATE_NOT) { + int pixel_size = view->GetPixelSize(bDistBetween(skid_set->GetBBoxCentre(), view->GetCamera()->GetPosition()), 1.0f); + if (4.0f < static_cast(pixel_size)) { + unsigned char intensityReduction; + if (10.0f < static_cast(pixel_size)) { + intensityReduction = 0; + } else { + intensityReduction = + static_cast(static_cast(256.0f - (pixel_size - 4.0f) * 42.666668f) & 0xff); + } + + skid_set->Render(view, intensityReduction); + SkidSetList.Remove(skid_set); + SkidSetList.AddHead(skid_set); + } + } + } +} diff --git a/src/Speed/Indep/Src/World/Skids.hpp b/src/Speed/Indep/Src/World/Skids.hpp index 0346dee8b..afa3d3748 100644 --- a/src/Speed/Indep/Src/World/Skids.hpp +++ b/src/Speed/Indep/Src/World/Skids.hpp @@ -5,7 +5,99 @@ #pragma once #endif +#include "Clans.hpp" +#include "Car.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct eView; +extern SlotPool *SkidSetSlotPool; + +class SkidSegment { + private: + // total size: 0x10 + float Position[3]; // offset 0x0, size 0xC + signed char DeltaPosition[3]; // offset 0xC, size 0x3 + unsigned char Intensity; // offset 0xF, size 0x1 + + public: + bVector3 *GetPosition() { + return reinterpret_cast(Position); + } + void SetIntensity(unsigned char intensity) { + Intensity = intensity; + } + unsigned char GetIntensity() { + return Intensity; + } + void SetPoints(bVector3 *position, bVector3 *delta_position); + void GetPoints(bVector3 *position, bVector3 *delta_position); + void GetEndPoints(bVector3 *left_point, bVector3 *right_point); +}; + +struct SkidMaker { + // total size: 0x4 + struct SkidSet *pSkidSet; // offset 0x0, size 0x4 + + void MakeSkid(Car *pCar, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity); + void MakeNoSkid(); +}; + +struct SkidSet : public bTNode { + // total size: 0xF0 + bVector3 LastNormal; // offset 0x8, size 0x10 + float LastSegmentLength; // offset 0x18, size 0x4 + Clan *pClan; // offset 0x1C, size 0x4 + bPNode *pClanNode; // offset 0x20, size 0x4 + SkidMaker *pSkidMaker; // offset 0x24, size 0x4 + int TheTerrainType; // offset 0x28, size 0x4 + bVector3 Position; // offset 0x2C, size 0x10 + bVector3 BBoxMax; // offset 0x3C, size 0x10 + bVector3 BBoxMin; // offset 0x4C, size 0x10 + SkidSegment SkidSegments[8]; // offset 0x5C, size 0x80 + int NumSkidSegments; // offset 0xDC, size 0x4 + bVector3 BBoxCentre; // offset 0xE0, size 0x10 + + void *operator new(size_t size) { + return bMalloc(SkidSetSlotPool); + } + void operator delete(void *ptr) { + bFree(SkidSetSlotPool, ptr); + } + + SkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity); + ~SkidSet(); + + int AddSegment(bVector3 *position, bVector3 *delta_position, bool skid_is_flaming, float intensity); + void FinishedAddingSkids(); + void Render(eView *view, unsigned char alpha); + int GetTerrainType() { + return TheTerrainType; + } + bVector3 *GetBBoxMax() { + return &BBoxMax; + } + bVector3 *GetBBoxMin() { + return &BBoxMin; + } + bVector3 *GetBBoxCentre() { + return &BBoxCentre; + } + void GetLastPoints(bVector3 *position, bVector3 *delta_position) { + SkidSegments[NumSkidSegments - 1].GetPoints(position, delta_position); + } + float GetLastIntensity() { + return static_cast(SkidSegments[NumSkidSegments - 1].GetIntensity()) * (1.0f / 255.0f); + } +}; + +SkidSet *CreateNewSkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity); void InitSkids(int max_skids); void CloseSkids(); +void DeleteThisSkid(SkidSet *skid_set); +void DeleteAllSkids(); +void RenderSkids(eView *view, Clan *clan); #endif diff --git a/src/Speed/Indep/Src/World/Track.cpp b/src/Speed/Indep/Src/World/Track.cpp index e69de29bb..e50245b9c 100644 --- a/src/Speed/Indep/Src/World/Track.cpp +++ b/src/Speed/Indep/Src/World/Track.cpp @@ -0,0 +1,110 @@ +#include "Track.hpp" + +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" + +void bEndianSwap32(void *value); + +enum TerrainType { + TERRAIN_TYPE_NONE = 0, + TERRAIN_TYPE_ROAD = 1, + TERRAIN_TYPE_ROAD_WET = 2, + TERRAIN_TYPE_ROAD_DRIFT = 3, + TERRAIN_TYPE_ROAD_SMOKE_1 = 4, + TERRAIN_TYPE_ROAD_SMOKE_2 = 5, + TERRAIN_TYPE_ROAD_SMOKE_3 = 6, + TERRAIN_TYPE_BRIDGE = 7, + TERRAIN_TYPE_DIRT = 8, + TERRAIN_TYPE_GRAVEL = 9, + TERRAIN_TYPE_ROUGH_ROAD = 10, + TERRAIN_TYPE_COBBLESTONE = 11, + TERRAIN_TYPE_STAIRS = 12, + TERRAIN_TYPE_PUDDLE = 13, + TERRAIN_TYPE_DEEP_WATER = 14, + TERRAIN_TYPE_GRASS = 15, + TERRAIN_TYPE_SIDEWALK = 16, + TERRAIN_TYPE_WOOD = 17, + TERRAIN_TYPE_PLASTIC = 18, + TERRAIN_TYPE_GLASS = 19, + TERRAIN_TYPE_SOLID_WALL = 20, + TERRAIN_TYPE_SEE_THROUGH_WALL = 21, + TERRAIN_TYPE_PLANT = 22, + TERRAIN_TYPE_POST = 23, + TERRAIN_TYPE_PILLAR = 24, + TERRAIN_TYPE_METAL_GRATE = 25, + TERRAIN_TYPE_METAL = 26, + TERRAIN_TYPE_CHAINLINK = 27, + TERRAIN_TYPE_CAR = 28, + NUM_TERRAIN_TYPES = 29, +}; + +struct TopologyCoordinate { + int HasTopology(const bVector2 *position); + float GetElevation(const bVector3 *position, enum TerrainType *type, bVector3 *normal, bool *point_valid); + + private: + int mData[2]; +}; + +// total size: 0x60 +struct TrackOBB { + inline void EndianSwap() { + bPlatEndianSwap(&TypeNameHash); + bPlatEndianSwap(&Matrix); + bPlatEndianSwap(&Dims); + } + + unsigned int TypeNameHash; + unsigned int Pad[3]; + bMatrix4 Matrix; + bVector3 Dims; +}; + +static char *TrackOBBTable = 0; +static int NumTrackOBBs = 0; +bChunkLoader bChunkLoaderTrackOBB(0x34191, LoaderTrackOBB, UnloaderTrackOBB); + +void EstablishRemoteCaffeineConnection() {} + +int GetNumTrackOBBs() { + return NumTrackOBBs; +} + +TrackOBB *GetTrackOBB(int index) { + return reinterpret_cast(TrackOBBTable + index * 0x60); +} + +int LoaderTrackOBB(bChunk *chunk) { + if (chunk->GetID() != 0x34191) { + return 0; + } + + NumTrackOBBs = static_cast(chunk->GetAlignedSize(16)) / 0x60u; + TrackOBBTable = chunk->GetAlignedData(16); + for (int n = 0; n < NumTrackOBBs; n++) { + TrackOBB *track_obb = reinterpret_cast(TrackOBBTable + n * 0x60); + track_obb->EndianSwap(); + } + + return 1; +} + +int UnloaderTrackOBB(bChunk *chunk) { + if (chunk->GetID() != 0x34191) { + return 0; + } + + NumTrackOBBs = 0; + TrackOBBTable = 0; + return 1; +} + +int TopologyCoordinate::HasTopology(const bVector2 *position) { + float test_elevation; + bVector3 test_position(position->x, position->y, 99999.1015625f); + bool point_valid; + + test_elevation = GetElevation(&test_position, 0, 0, &point_valid); + (void)test_elevation; + return point_valid; +} diff --git a/src/Speed/Indep/Src/World/Track.hpp b/src/Speed/Indep/Src/World/Track.hpp index 31877c32d..41ee4dc6a 100644 --- a/src/Speed/Indep/Src/World/Track.hpp +++ b/src/Speed/Indep/Src/World/Track.hpp @@ -5,6 +5,14 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" + +struct TrackOBB; + void EstablishRemoteCaffeineConnection(); +int GetNumTrackOBBs(); +TrackOBB *GetTrackOBB(int index); +int LoaderTrackOBB(bChunk *chunk); +int UnloaderTrackOBB(bChunk *chunk); #endif diff --git a/src/Speed/Indep/Src/World/TrackInfo.cpp b/src/Speed/Indep/Src/World/TrackInfo.cpp index e69de29bb..3cd9ee1de 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.cpp +++ b/src/Speed/Indep/Src/World/TrackInfo.cpp @@ -0,0 +1,105 @@ +#include "TrackInfo.hpp" + +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +static TrackInfo *TrackInfoTable = 0; +static unsigned int NumTrackInfo = 0; +TrackInfo *LoadedTrackInfo = 0; +bChunkLoader bChunkLoaderTrackInfo(0x34201, TrackInfo::LoaderTrackInfo, TrackInfo::UnloaderTrackInfo); + +int TrackInfo::LoaderTrackInfo(bChunk *chunk) { + int i; + int j; + + if (chunk->GetID() == 0x34201) { + TrackInfoTable = reinterpret_cast(chunk->GetData()); + NumTrackInfo = chunk->GetSize() / sizeof(TrackInfo); + + for (int n = 0; n < static_cast(NumTrackInfo); n++) { + TrackInfo *info = &TrackInfoTable[n]; + bPlatEndianSwap(&info->LocationNumber); + bPlatEndianSwap(reinterpret_cast(&info->LocationName)); + bPlatEndianSwap(reinterpret_cast(&info->DriftType)); + bPlatEndianSwap(&info->TrackNumber); + bPlatEndianSwap(&info->SameAsTrackNumber); + bPlatEndianSwap(&info->TimeToBeatForwards_ms); + bPlatEndianSwap(&info->TimeToBeatReverse_ms); + bPlatEndianSwap(&info->ScoreToBeatForwards_DriftOnly); + bPlatEndianSwap(&info->ScoreToBeatReverse_DriftOnly); + bPlatEndianSwap(&info->SunInfoNameHash); + bPlatEndianSwap(&info->UsageFlags); + bPlatEndianSwap(&info->TrackMapCalibrationUpperLeft); + bPlatEndianSwap(&info->TrackMapCalibrationMapWidthMetres); + bPlatEndianSwap(&info->TrackMapCalibrationRotation); + bPlatEndianSwap(&info->TrackMapStartLineAngle); + bPlatEndianSwap(&info->TrackMapFinishLineAngle); + bPlatEndianSwap(reinterpret_cast(&info->ForwardDifficulty)); + bPlatEndianSwap(reinterpret_cast(&info->ReverseDifficulty)); + bPlatEndianSwap(&info->NumSecondsBeforeShortcutsAllowed); + bPlatEndianSwap(&info->nDriftSecondsMin); + bPlatEndianSwap(&info->nDriftSecondsMax); + + i = 0; + do { + j = 0; + do { + bPlatEndianSwap(&info->OverrideStartingRouteForAI[i][j]); + j += 1; + } while (j < 4); + i += 1; + } while (i < 2); + + i = 0; + do { + j = 0; + do { + bPlatEndianSwap(&info->MaxTrafficCars[i][j]); + j += 1; + } while (j < 2); + i += 1; + } while (i < 4); + + i = 0; + do { + bPlatEndianSwap(&info->TrafficAllowedNearStartLine[i]); + i += 1; + } while (i < 2); + + i = 0; + do { + bPlatEndianSwap(&info->TrafficMinInitialDistanceBetweenCars[i]); + i += 1; + } while (i < 2); + + i = 0; + do { + bPlatEndianSwap(&info->TrafficMinInitialDistanceFromStartLine[i]); + i += 1; + } while (i < 2); + } + + return 1; + } + return 0; +} + +TrackInfo *TrackInfo::GetTrackInfo(int track_number) { + for (int n = 0; n < static_cast(NumTrackInfo); n++) { + TrackInfo *info = &TrackInfoTable[n]; + if (info->TrackNumber == track_number) { + return info; + } + } + + return 0; +} + +int TrackInfo::UnloaderTrackInfo(bChunk *chunk) { + if (chunk->GetID() == 0x34201) { + TrackInfoTable = 0; + NumTrackInfo = 0; + return 1; + } + return 0; +} diff --git a/src/Speed/Indep/Src/World/TrackInfo.hpp b/src/Speed/Indep/Src/World/TrackInfo.hpp index ae235e386..d07d41e23 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.hpp +++ b/src/Speed/Indep/Src/World/TrackInfo.hpp @@ -5,8 +5,12 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +class TrackInfo; +extern TrackInfo *LoadedTrackInfo; + enum eLocationName { UPPER_CLASS = 0, CITY_CORE = 1, @@ -41,6 +45,16 @@ class TrackInfo { return LoadedTrackInfo; } + static int LoaderTrackInfo(bChunk *chunk); + static int UnloaderTrackInfo(bChunk *chunk); + + static int GetLoadedTrackNumber() { + if (LoadedTrackInfo) { + return LoadedTrackInfo->TrackNumber; + } + return 0; + } + char Name[32]; // offset 0x0, size 0x20 char TrackDirectory[32]; // offset 0x20, size 0x20 char RegionName[8]; // offset 0x40, size 0x8 diff --git a/src/Speed/Indep/Src/World/TrackPath.cpp b/src/Speed/Indep/Src/World/TrackPath.cpp index e69de29bb..4f5ea2aff 100644 --- a/src/Speed/Indep/Src/World/TrackPath.cpp +++ b/src/Speed/Indep/Src/World/TrackPath.cpp @@ -0,0 +1,267 @@ +#include "Speed/Indep/Src/World/TrackPath.hpp" +#include "Scenery.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); +BOOL bBoundingBoxOverlapping(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *bbox2_min, const bVector2 *bbox2_max); +bool bIsPointInPoly(const bVector2 *point, const bVector2 *points, int num_points); +void bEndianSwap16(void *value); +void bEndianSwap32(void *value); +void bPlatEndianSwap(bVector2 *value); +void NotifyGameZonesChanged(); + +static inline TrackPathZone *NextTrackPathZone(TrackPathZone *zone) { + return reinterpret_cast(reinterpret_cast(zone) + zone->MemoryImageSize); +} + +static inline char *GetTrackPathBarrierData(TrackPathBarrier *barriers, int index) { + return reinterpret_cast(barriers) + index * 0x18; +} + +TrackPathZone *zoneB[2]; +TrackPathManager TheTrackPathManager; +bChunkLoader bChunkLoaderTrackPath(0x80034147, LoaderTrackPath, UnloaderTrackPath); +bChunkLoader bChunkLoaderTrackPathBarriers(0x3414D, LoaderTrackPath, UnloaderTrackPath); + +void TrackPathManager::Clear() { + NumZones = 0; + SizeofZones = 0; + pZones = nullptr; + bMemSet(ZoneInfoTable, 0, sizeof(ZoneInfoTable)); + MostCachedZones = 0; + pBarriers = nullptr; + NumBarriers = 0; + zoneB[0] = 0; + zoneB[1] = 0; +} + +int TrackPathManager::Loader(bChunk *chunk) { + if (chunk->GetID() == 0x80034147) { + bChunk *last_chunk = chunk->GetLastChunk(); + + for (chunk = chunk->GetFirstChunk(); chunk != last_chunk; chunk = chunk->GetNext()) { + if (chunk->GetID() == 0x3414A) { + pZones = reinterpret_cast(chunk->GetData()); + TrackPathZone *zone = pZones; + SizeofZones = chunk->GetSize(); + NumZones = 0; + + for (; zone < GetLastZone(); zone = zone->GetMemoryImageNext()) { + bEndianSwap32(&zone->Type); + bPlatEndianSwap(&zone->Position); + bPlatEndianSwap(&zone->Direction); + bEndianSwap32(&zone->Elevation); + bEndianSwap16(&zone->VisitInfo); + bEndianSwap16(&zone->NumPoints); + bEndianSwap16(&zone->MemoryImageSize); + bPlatEndianSwap(&zone->BBoxMin); + bPlatEndianSwap(&zone->BBoxMax); + for (int n = 0; n < zone->NumPoints; n++) { + bPlatEndianSwap(&zone->Points[n]); + } + for (int n = 0; n < 4; n++) { + bEndianSwap32(reinterpret_cast(n * sizeof(int) + reinterpret_cast(zone) + 0x30)); + } + NumZones += 1; + } + } + } + BuildZoneInfoTable(); + return true; + } + + if (chunk->GetID() == 0x3414D) { + pBarriers = reinterpret_cast(chunk->GetData()); + int i; + unsigned int size = chunk->GetSize(); + NumBarriers = size / 0x18; + for (i = 0; i < NumBarriers; i++) { + pBarriers[i].EndianSwap(); + } + return true; + } + + return false; +} + +int TrackPathManager::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x80034147) { + Clear(); + NotifyGameZonesChanged(); + return true; + } + + if (chunk->GetID() == 0x3414D) { + pBarriers = nullptr; + NumBarriers = 0; + return true; + } + + return false; +} + +void TrackPathManager::DisableAllBarriers() { + for (int i = 0; i < NumBarriers; i++) { + GetTrackPathBarrierData(pBarriers, i)[0x10] = 0; + } +} + +void TrackPathManager::EnableBarriers(const char *group_name) { + unsigned int group_name_hash = bStringHash(group_name); + for (int i = 0; i < NumBarriers; i++) { + char *barrier = GetTrackPathBarrierData(pBarriers, i); + if (*reinterpret_cast(barrier + 0x14) == group_name_hash) { + barrier[0x10] = 1; + + void *scenery_group = FindSceneryGroup(group_name_hash); + barrier[0x12] = scenery_group && *reinterpret_cast(reinterpret_cast(scenery_group) + 0x11); + } + } +} + +void TrackPathManager::BuildZoneInfoTable() { + for (int type = 0; type < NUM_TRACK_PATH_ZONES; type++) { + ZoneInfo *zone_info = &ZoneInfoTable[type]; + zone_info->NumZones = 0; + + for (TrackPathZone *zone = pZones; zone < GetLastZone(); zone = zone->GetMemoryImageNext()) { + if (zone->GetType() == static_cast(type)) { + if (zone_info->NumZones == 0) { + zone_info->pFirstZone = zone; + } + zone_info->pLastZone = zone->GetMemoryImageNext(); + zone_info->NumZones += 1; + } + } + } + + NotifyGameZonesChanged(); +} + +TrackPathZone *TrackPathManager::FindZone(const bVector2 *position, eTrackPathZoneType zone_type, TrackPathZone *prev_zone) { + ZoneInfo *zone_info = &ZoneInfoTable[zone_type]; + bool cache_valid; + + if (!position) { + cache_valid = false; + } else if (bBoundingBoxIsInside(&zone_info->CachedBBoxMin, &zone_info->CachedBBoxMax, position, 0.0f)) { + cache_valid = zone_info->NumCachedZones < 9; + } else { + const float cached_radius = 64.0f; + TrackPathZone *first_zone; + TrackPathZone *last_zone; + + last_zone = zone_info->pLastZone; + first_zone = zone_info->pFirstZone; + + zone_info->CachedBBoxMin.x = position->x - cached_radius; + zone_info->CachedBBoxMin.y = position->y - cached_radius; + zone_info->CachedBBoxMax.x = position->x + cached_radius; + zone_info->CachedBBoxMax.y = position->y + cached_radius; + zone_info->NumCachedZones = 0; + zone_info->NumFullRebuilds += 1; + + for (TrackPathZone *zone = first_zone; zone < last_zone; zone = zone->GetMemoryImageNext()) { + if (bBoundingBoxOverlapping(&zone_info->CachedBBoxMin, &zone_info->CachedBBoxMax, &zone->BBoxMin, &zone->BBoxMax)) { + if (zone_info->NumCachedZones < 8) { + zone->CachedIndex = static_cast(zone_info->NumCachedZones); + zone_info->CachedZones[zone_info->NumCachedZones] = zone; + } + zone_info->NumCachedZones += 1; + } + } + + cache_valid = zone_info->NumCachedZones < 9; + MostCachedZones = bMax(MostCachedZones, zone_info->NumCachedZones); + } + + TrackPathZone *found_zone = 0; + if (!cache_valid) { + TrackPathZone *last_zone = zone_info->pLastZone; + TrackPathZone *first_zone; + + first_zone = zone_info->pFirstZone; + zone_info->NumCacheRebuilds += 1; + if (prev_zone) { + first_zone = prev_zone->GetMemoryImageNext(); + } + + TrackPathZone *zone = first_zone; + while (zone < last_zone && position && + (!bBoundingBoxIsInside(&zone->BBoxMin, &zone->BBoxMax, position, 0.0f) || !zone->IsPointInside(position))) { + zone = zone->GetMemoryImageNext(); + } + found_zone = zone; + } else { + int first_zone_index = 0; + zone_info->NumCacheHits += 1; + if (prev_zone) { + first_zone_index = prev_zone->CachedIndex + 1; + } + + for (int index = first_zone_index; index < zone_info->NumCachedZones; index++) { + TrackPathZone *zone = zone_info->CachedZones[index]; + if (bBoundingBoxIsInside(&zone->BBoxMin, &zone->BBoxMax, position, 0.0f) && zone->IsPointInside(position)) { + found_zone = zone; + break; + } + } + } + + return found_zone; +} + +void TrackPathManager::ResetZoneVisitInfos() { + for (TrackPathZone *zone = pZones; zone < GetLastZone(); zone = zone->GetMemoryImageNext()) { + zone->SetVisitInfo(0); + } +} + +bool TrackPathZone::IsPointInside(const bVector2 *point) { + return bIsPointInPoly(point, Points, NumPoints); +} + +float TrackPathZone::GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a, bVector2 *segment_point_b) { + int Closest0 = -1; + int Closest1 = -1; + float d0 = 1.0e30f; + float len; + + for (int n = 0; n < NumPoints; n++) { + bVector2 *p0 = &Points[n % NumPoints]; + bVector2 *p1 = &Points[(n + 1) % NumPoints]; + bVector2 v = *p0 - *point; + bVector2 r(p1->y - p0->y, p0->x - p1->x); + + bNormalize(&r, &r); + len = bDot(&r, &v); + bVector2 InPoint = r * (len * 0.999f) + *point; + bVector2 InPoint2 = r * (len * 1.001f) + *point; + len = bAbs(len); + + if (len < d0 && (IsPointInside(&InPoint) || IsPointInside(&InPoint2))) { + Closest0 = n % NumPoints; + Closest1 = (n + 1) % NumPoints; + d0 = len; + } + } + + if (Closest0 == -1 || Closest1 == -1) { + return -1.0f; + } + + bCopy(segment_point_a, &Points[Closest0]); + bCopy(segment_point_b, &Points[Closest1]); + return d0; +} + +void TrackPathInitRemoteCaffeineConnection() {} + +int LoaderTrackPath(bChunk *chunk) { + return TheTrackPathManager.Loader(chunk); +} + +int UnloaderTrackPath(bChunk *chunk) { + return TheTrackPathManager.Unloader(chunk); +} diff --git a/src/Speed/Indep/Src/World/TrackPath.hpp b/src/Speed/Indep/Src/World/TrackPath.hpp index 93f225503..419daaca1 100644 --- a/src/Speed/Indep/Src/World/TrackPath.hpp +++ b/src/Speed/Indep/Src/World/TrackPath.hpp @@ -5,7 +5,9 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" enum eTrackPathZoneType { NUM_TRACK_PATH_ZONES = 15, @@ -26,6 +28,22 @@ enum eTrackPathZoneType { TRACK_PATH_ZONE_RESET = 0, }; +// total size: 0x18 +struct TrackPathBarrier { + bVector2 Points[2]; // offset 0x0, size 0x10 + char Enabled; // offset 0x10, size 0x1 + char Pad; // offset 0x11, size 0x1 + char PlayerBarrier; // offset 0x12, size 0x1 + char LeftHanded; // offset 0x13, size 0x1 + unsigned int GroupHash; // offset 0x14, size 0x4 + + void EndianSwap() { + bPlatEndianSwap(&Points[0]); + bPlatEndianSwap(&Points[1]); + bPlatEndianSwap(&GroupHash); + } +}; + // total size: 0x244 class TrackPathZone { public: @@ -48,6 +66,26 @@ class TrackPathZone { void GetOpposite(bVector2 *in0, bVector2 *in1, bVector2 *opp0, bVector2 *opp1); bool GetIntercept(bVector2 &InterceptPoint, const bVector2 *Start, const bVector2 *Direction); bool IsPointInside(const bVector2 *point); + float GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a, bVector2 *segment_point_b); + float GetElevation() { + return Elevation; + } + + eTrackPathZoneType GetType() { + return Type; + } + + int GetMemoryImageSize() { + return MemoryImageSize; + } + + void SetVisitInfo(int v) { + VisitInfo = v; + } + + TrackPathZone *GetMemoryImageNext() { + return reinterpret_cast(reinterpret_cast(this) + GetMemoryImageSize()); + } int GetData(int index) { return Data[index]; @@ -67,12 +105,19 @@ class TrackPathManager { bVector2 CachedBBoxMax; int NumCachedZones; int NumCacheHits; - int NumCacheRebuilds; int NumFullRebuilds; + int NumCacheRebuilds; TrackPathZone *CachedZones[8]; }; public: + TrackPathManager() { + Clear(); + } + + int Loader(bChunk *chunk); + int Unloader(bChunk *chunk); + void Clear(); void EnableBarriers(const char *group_name); void DisableAllBarriers(); void BuildZoneInfoTable(); @@ -82,19 +127,23 @@ class TrackPathManager { void Close() {} private: - // TrackPathZone *GetLastZone() {} - - int NumZones; // offset 0x0, size 0x4 - int SizeofZones; // offset 0x4, size 0x4 - TrackPathZone *pZones; // offset 0x8, size 0x4 - ZoneInfo ZoneInfoTable[15]; // offset 0xC, size 0x474 - int MostCachedZones; // offset 0x480, size 0x4 - int NumBarriers; // offset 0x484, size 0x4 - struct TrackPathBarrier *pBarriers; // offset 0x488, size 0x4 + TrackPathZone *GetLastZone() { + return reinterpret_cast(reinterpret_cast(pZones) + SizeofZones); + } + + int NumZones; // offset 0x0, size 0x4 + int SizeofZones; // offset 0x4, size 0x4 + TrackPathZone *pZones; // offset 0x8, size 0x4 + ZoneInfo ZoneInfoTable[15]; // offset 0xC, size 0x474 + int MostCachedZones; // offset 0x480, size 0x4 + int NumBarriers; // offset 0x484, size 0x4 + TrackPathBarrier *pBarriers; // offset 0x488, size 0x4 }; extern TrackPathManager TheTrackPathManager; void TrackPathInitRemoteCaffeineConnection(); +int LoaderTrackPath(bChunk *chunk); +int UnloaderTrackPath(bChunk *chunk); #endif diff --git a/src/Speed/Indep/Src/World/TrackPositionMarker.cpp b/src/Speed/Indep/Src/World/TrackPositionMarker.cpp index e69de29bb..8f3f86a66 100644 --- a/src/Speed/Indep/Src/World/TrackPositionMarker.cpp +++ b/src/Speed/Indep/Src/World/TrackPositionMarker.cpp @@ -0,0 +1,93 @@ +#include "TrackPositionMarker.hpp" + +#include "TrackInfo.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" + +bTList TrackPositionMarkerList; +bChunkLoader bChunkLoaderTrackPositionMarkers(0x34146, LoaderTrackPositionMarkers, UnloaderTrackPositionMarkers); + +static void NotifyTrackMarkersChanged() {} + +void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *, unsigned int), unsigned int tag) { + for (TrackPositionMarker *marker = TrackPositionMarkerList.GetHead(); marker != TrackPositionMarkerList.EndOfList(); + marker = marker->GetNext()) { + if (!callback(marker, tag)) { + break; + } + } +} + +int LoaderTrackPositionMarkers(bChunk *chunk) { + if (chunk->GetID() == 0x34146) { + TrackPositionMarker *marker_table = reinterpret_cast(chunk->GetAlignedData(0x10)); + int num_markers = chunk->GetAlignedSize(0x10) / sizeof(TrackPositionMarker); + + for (int n = 0; n < num_markers; n++) { + TrackPositionMarker *marker = &marker_table[n]; + bPlatEndianSwap(&marker->NameHash); + bPlatEndianSwap(&marker->Param); + bPlatEndianSwap(&marker->Position); + bPlatEndianSwap(&marker->Angle); + bPlatEndianSwap(&marker->TrackNumber); + TrackPositionMarkerList.AddTail(marker); + } + + NotifyTrackMarkersChanged(); + return 1; + } + + return 0; +} + +int UnloaderTrackPositionMarkers(bChunk *chunk) { + if (chunk->GetID() == 0x34146) { + TrackPositionMarker *marker_table = reinterpret_cast(chunk->GetAlignedData(0x10)); + int num_markers = chunk->GetAlignedSize(0x10) / sizeof(TrackPositionMarker); + + for (int n = 0; n < num_markers; n++) { + TrackPositionMarkerList.Remove(&marker_table[n]); + } + + NotifyTrackMarkersChanged(); + return 1; + } + + return 0; +} + +int GetNumTrackPositionMarkers(int track_number, unsigned int name_hash) { + int num_markers = 0; + + for (TrackPositionMarker *p = TrackPositionMarkerList.GetHead(); p != TrackPositionMarkerList.EndOfList(); + p = p->GetNext()) { + if (p->NameHash == name_hash && (track_number == 0 || p->TrackNumber == track_number)) { + num_markers += 1; + } + } + + return num_markers; +} + +TrackPositionMarker *GetTrackPositionMarker(int track_number, unsigned int name_hash, int index) { + int num_markers = 0; + + for (TrackPositionMarker *p = TrackPositionMarkerList.GetHead(); p != TrackPositionMarkerList.EndOfList(); + p = p->GetNext()) { + if (p->NameHash == name_hash && (p->TrackNumber == 0 || p->TrackNumber == track_number)) { + if (num_markers == index) { + return p; + } + + num_markers += 1; + } + } + + return 0; +} + +TrackPositionMarker *GetTrackPositionMarker(unsigned int name_hash, int index) { + int track_number = TrackInfo::GetLoadedTrackNumber(); + + return GetTrackPositionMarker(track_number, name_hash, index); +} diff --git a/src/Speed/Indep/Src/World/TrackPositionMarker.hpp b/src/Speed/Indep/Src/World/TrackPositionMarker.hpp index f0c95d5db..bb20b48a9 100644 --- a/src/Speed/Indep/Src/World/TrackPositionMarker.hpp +++ b/src/Speed/Indep/Src/World/TrackPositionMarker.hpp @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" @@ -20,7 +21,12 @@ struct TrackPositionMarker : public bTNode { int Padding3; // offset 0x2C, size 0x4 }; +int LoaderTrackPositionMarkers(bChunk *chunk); +int UnloaderTrackPositionMarkers(bChunk *chunk); +void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *, unsigned int), unsigned int tag); int GetNumTrackPositionMarkers(int track_number, unsigned int name_hash); +TrackPositionMarker *GetTrackPositionMarker(int track_number, unsigned int name_hash, int index); +TrackPositionMarker *GetTrackPositionMarker(unsigned int name_hash, int index); extern bTList TrackPositionMarkerList; diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index e69de29bb..d5dba217b 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -0,0 +1,2488 @@ +#include "TrackStreamer.hpp" + +#include "TrackPath.hpp" +#include "VisibleSection.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Misc/QueuedFile.hpp" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/Src/Misc/bFile.hpp" +#include "Speed/Indep/bWare/Inc/bDebug.hpp" +#include "Speed/Indep/bWare/Inc/Espresso.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#ifdef EA_PLATFORM_GAMECUBE +#include "dolphin/PPCArch.h" +#include "dolphin/os/OSCache.h" +#endif + +#include + +extern BOOL bMemoryTracing; +extern int ScenerySectionLODOffset; +extern int SeeulatorToolActive; +extern int ScenerySectionToBlink; +extern int RealLoopCounter; +extern bool PostLoadFixupDisabled; +extern int AllowDuplicateSolids; +extern int ForceHoleFillerMethod; +extern int WaitForFrameBufferSwapDisabled; +extern int WaitUntilRenderingDoneDisabled; +extern int TrackStreamerRemoteCaffeinating; +extern unsigned int eFrameCounter; +int Get2PlayerSectionNumber(int section_number); +char *GetScenerySectionName(char *name, int section_number); +char *GetScenerySectionName(int section_number); +void PostLoadFixup(); +void SetDuplicateTextureWarning(BOOL enabled); +bool LoadTempPermChunks(bChunk **ppchunks, int *psizeof_chunks, int allocation_params, const char *debug_name); +bool DoLinesIntersect(const bVector2 &line1_start, const bVector2 &line1_end, const bVector2 &line2_start, const bVector2 &line2_end); +void eWaitUntilRenderingDone(); +void MoveChunks(bChunk *dest_chunks, bChunk *source_chunks, int sizeof_chunks, const char *debug_name); +void bSetMemoryPoolOverrideInfo(int pool_num, MemoryPoolOverrideInfo *override_info); +void UnloadChunks(bChunk *chunks, int sizeof_chunks, const char *debug_name); +void SetQueuedFileMinPriority(int priority); +void NotifySkyLoader(); +void BlockWhileQueuedFileBusy(); +int GetBoundarySectionNumber(int section_number, const char *platform_name); +int LoaderTrackStreamer(bChunk *chunk); +int UnloaderTrackStreamer(bChunk *chunk); +extern int QueuedFileDefaultPriority; + +static unsigned int prev_need_loading_bar_26275 = 0; +static const float kMaxDistance_TrackStreamer = 3.4028235e+38f; +static const float kVelocityEpsilon_TrackStreamer = 0.0f; +static const float kFuturePositionScale_TrackStreamer = 0.5f; +static const float kPredictionScaleA_TrackStreamer = 1.0f; +static const float kPredictionScaleB_TrackStreamer = 1.0f; +static const float kLoadingBarDistanceThreshold_TrackStreamer = 15.0f; +static const float kLoadingBarSpeedThreshold_TrackStreamer = 100.0f; +static const float kSwitchZoneFarLoadThreshold_TrackStreamer = 0.1f; +static const float kPredictedZoneScale_TrackStreamer = 1.5f; +static const float kPredictedZoneMaxDistance_TrackStreamer = 100.0f; +static const float kPredictedZoneStopProjectSpeed_TrackStreamer = 178.81091f; +static const float kPredictedZoneEqualEpsilon_TrackStreamer = 0.001f; +static VisibleSectionBitTable CurrentVisibleSectionTableMem; +TrackStreamer TheTrackStreamer; +bChunkLoader bChunkLoaderTrackStreamingSection(0x34110, LoaderTrackStreamer, UnloaderTrackStreamer); +bChunkLoader bChunkLoaderTrackStreamingDiscBundle(0x34113, LoaderTrackStreamer, UnloaderTrackStreamer); +bChunkLoader bChunkLoaderTrackStreamingInfo(0x34111, LoaderTrackStreamer, UnloaderTrackStreamer); +bChunkLoader bChunkLoaderTrackStreamingBarriers(0x34112, LoaderTrackStreamer, UnloaderTrackStreamer); + +static inline char GetScenerySectionLetter_TrackStreamer(int section_number) { + return static_cast(section_number / 100 + 'A' - 1); +} + +static inline bool IsRegularScenerySection_TrackStreamer(int section_number) { + char section_letter = GetScenerySectionLetter_TrackStreamer(section_number); + return section_letter >= 'A' && section_letter < 'U'; +} + +static inline bool IsTextureSection_TrackStreamer(int section_number) { + char section_letter = GetScenerySectionLetter_TrackStreamer(section_number); + return section_letter == 'Y' || section_letter == 'W'; +} + +static inline bool IsLibrarySection_TrackStreamer(int section_number) { + char section_letter = GetScenerySectionLetter_TrackStreamer(section_number); + return section_letter == 'X' || section_letter == 'U'; +} + +static inline short GetScenerySectionNumber_TrackStreamer(char section_letter, int subsection_number) { + return static_cast((section_letter - 'A' + 1) * 100 + subsection_number); +} + +static inline bool IsLODScenerySectionNumber(int section_number) { + int subsection_number = GetScenerySubsectionNumber(section_number); + return subsection_number >= ScenerySectionLODOffset && subsection_number < ScenerySectionLODOffset * 2; +} + +static inline bool IsLoadingBarSection_TrackStreamer(int section_number) { + if (!IsRegularScenerySection_TrackStreamer(section_number)) { + return false; + } + + int subsection_number = section_number % 100; + return (subsection_number > 0 && subsection_number < ScenerySectionLODOffset) || + (ScenerySectionLODOffset <= subsection_number && subsection_number < ScenerySectionLODOffset * 2); +} + +static inline void DisableWaitForFrameBufferSwap() { + WaitForFrameBufferSwapDisabled = 1; +} + +static inline void EnableWaitForFrameBufferSwap() { + WaitForFrameBufferSwapDisabled = 0; +} + +static inline void eAllowDuplicateSolids(bool enable) { + if (enable) { + AllowDuplicateSolids += 1; + } else { + AllowDuplicateSolids -= 1; + } +} + +bool DoLinesIntersect(const bVector2 &line1_start, const bVector2 &line1_end, const bVector2 &line2_start, const bVector2 &line2_end) { + float dy1 = line1_end.y - line1_start.y; + float dx2 = line2_end.x - line2_start.x; + float dx1 = line1_end.x - line1_start.x; + float dy2 = line2_end.y - line2_start.y; + float den = dx1 * dy2 - dy1 * dx2; + + if (den != 0.0f) { + float dx3 = line1_start.x - line2_start.x; + float dy3 = line1_start.y - line2_start.y; + float r = (dy3 * dx2 - dx3 * dy2) / den; + if (0.0f <= r && r <= 1.0f) { + float s = (dy3 * dx1 - dx3 * dy1) / den; + if (0.0f <= s && s <= 1.0f) { + return true; + } + } + } + + return false; +} + +inline bool TrackStreamingBarrier::Intersects(const bVector2 *pointa, const bVector2 *pointb) { + return DoLinesIntersect(Points[0], Points[1], *pointa, *pointb); +} + +struct bBitTableLayout_TrackStreamer { + int NumBits; + uint8 *Bits; +}; + +void bBitTable::ClearTable() { + bMemSet(Bits, 0, NumBits >> 3); +} + +struct bMemoryTraceAllocatePacket { + int PoolID; + int MemoryAddress; + int Size; + int DebugLine; + int AllocationNumber; + char DebugText[48]; +}; + +TSMemoryPool::TSMemoryPool(int address, int size, const char *debug_name, int pool_num) { + PoolNum = pool_num; + DebugName = debug_name; + TotalSize = size; + TracingEnabled = true; + Updated = false; + AllocationNumber = 0; + AmountFree = size; + LargestFree = size; + NeedToRecalcLargestFree = false; + + for (int i = 0; i < 192; i++) { + UnusedNodeList.AddTail(&MemoryNodes[i]); + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceNewPoolPacket packet; + packet.PoolID = reinterpret_cast(this); + bMemSet(packet.Name, 0, sizeof(packet.Name)); + bStrNCpy(packet.Name, debug_name, sizeof(packet.Name) - 1); + bFunkCallASync("CODEINE", 0x19, &packet, sizeof(packet)); + } + + GetNewNode(address, size, false, 0); + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceFreePacket packet; + bMemSet(&packet, 0, sizeof(packet)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = static_cast(address); + packet.Size = size; + bFunkCallASync("CODEINE", 0x1b, &packet, sizeof(packet)); + } + + bMemSet(&OverrideInfo, 0, sizeof(OverrideInfo)); + OverrideInfo.Name = DebugName; + OverrideInfo.Pool = this; + OverrideInfo.Address = address; + OverrideInfo.Size = size; + OverrideInfo.Malloc = OverrideMalloc; + OverrideInfo.Free = OverrideFree; + OverrideInfo.GetAmountFree = OverrideGetAmountFree; + OverrideInfo.GetLargestFreeBlock = OverrideGetLargestFreeBlock; + bSetMemoryPoolOverrideInfo(PoolNum, &OverrideInfo); +} + +TSMemoryNode *TSMemoryPool::GetNewNode(int address, int size, bool allocated, const char *debug_name) { + TSMemoryNode *node = UnusedNodeList.RemoveHead(); + + node->Address = address; + node->Size = size; + node->Allocated = allocated; + bSafeStrCpy(node->DebugName, debug_name, sizeof(node->DebugName)); + return node; +} + +void TSMemoryPool::RemoveNode(TSMemoryNode *node) { + UnusedNodeList.AddTail(node); +} + +inline bool TSMemoryNode::IsFree() { + return !Allocated; +} + +inline bool TSMemoryNode::IsAllocated() { + return Allocated; +} + +inline bool TSMemoryNode::Contains(int address) { + return address >= Address && address < Address + Size; +} + +void *TSMemoryPool::Malloc(int size, const char *debug_name, bool best_fit, bool allocate_from_top, int address) { + TSMemoryNode *found_node = 0; + TSMemoryNode *new_node; + int new_bottom_size; + int new_top_size; + + size = (size + 0x7f) & ~0x7f; + + if (address != 0) { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size >= size && node->Contains(address)) { + found_node = node; + break; + } + } + } else if (best_fit) { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size >= size && (!found_node || found_node->Size - size > node->Size - size)) { + found_node = node; + } + } + } else if (allocate_from_top) { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size >= size) { + found_node = node; + break; + } + } + } else { + for (TSMemoryNode *node = NodeList.GetTail(); node != NodeList.EndOfList(); node = node->GetPrev()) { + if (node->IsFree() && node->Size >= size) { + found_node = node; + break; + } + } + } + + if (!found_node) { + return 0; + } + + if (address == 0) { + if (allocate_from_top) { + address = found_node->Address; + } else { + address = found_node->Address + found_node->Size - size; + } + } + + AmountFree -= size; + if (found_node->Size == LargestFree) { + NeedToRecalcLargestFree = true; + } + + new_node = GetNewNode(address, size, true, debug_name); + new_node->AddAfter(found_node); + + new_bottom_size = address - found_node->Address; + new_top_size = found_node->Address + found_node->Size - (address + size); + found_node->Size = new_bottom_size; + if (new_bottom_size == 0) { + NodeList.Remove(found_node); + RemoveNode(found_node); + } + + if (new_top_size != 0) { + TSMemoryNode *top_node = GetNewNode(address + size, new_top_size, false, 0); + top_node->AddAfter(new_node); + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceAllocatePacket send_packet; + bMemoryTraceAllocatePacket packet; + bMemoryTraceAllocatePacket *fake_match = &packet; + int extra_len; + int n; + unsigned char *p; + + memset(fake_match, 0, sizeof(packet)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = address; + packet.Size = size; + packet.AllocationNumber = AllocationNumber; + send_packet = packet; + p = reinterpret_cast(send_packet.DebugText); + n = sizeof(send_packet.DebugText); + bMemSet(p, 0, n); + if (debug_name) { + bStrNCpy(reinterpret_cast(p), debug_name, n - 1); + } + extra_len = bStrLen(reinterpret_cast(p)) + 0x15; + bFunkCallASync("CODEINE", 0x1c, &send_packet, extra_len); + } + + Updated = true; + AllocationNumber += 1; + return reinterpret_cast(address); +} + +inline void *TSMemoryPool::OverrideMalloc(void *pool, int size, const char *debug_text, int debug_line, int allocation_params) { + register int user_alignment_offset; + (void)debug_line; + user_alignment_offset = bMemoryGetAlignmentOffset(allocation_params); + + if (user_alignment_offset != 0) { + char *p = reinterpret_cast(static_cast(pool)->Malloc(size + 0x80, debug_text, bMemoryGetBestFit(allocation_params), + bMemoryGetTopBit(allocation_params), 0)); + return &p[0x80 - user_alignment_offset]; + } + + return static_cast(pool)->Malloc(size, debug_text, bMemoryGetBestFit(allocation_params), bMemoryGetTopBit(allocation_params), 0); +} + +void TSMemoryPool::OverrideFree(void *pool, void *ptr) { + static_cast(pool)->Free(reinterpret_cast(reinterpret_cast(ptr) & ~static_cast(0x7F))); +} + +int TSMemoryPool::OverrideGetAmountFree(void *pool) { + return static_cast(pool)->GetAmountFree(); +} + +int TSMemoryPool::OverrideGetLargestFreeBlock(void *pool) { + return static_cast(pool)->GetLargestFreeBlock(); +} + +int TSMemoryPool::GetAmountFree() { + int amount_free; + + (void)amount_free; + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + node->IsFree(); + } + return AmountFree; +} + +int TSMemoryPool::GetLargestFreeBlock() { + if (NeedToRecalcLargestFree) { + int largest_free = 0; + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size > largest_free) { + largest_free = node->Size; + } + } + LargestFree = largest_free; + NeedToRecalcLargestFree = false; + } + + int largest_free = 0; + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size > largest_free) { + largest_free = node->Size; + } + } + return LargestFree; +} + +void TSMemoryPool::Free(void *memory) { + int address = reinterpret_cast(memory); + Updated = true; + + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->Address == address) { + int size; + TSMemoryNode *prev_node = node->GetPrev(); + + node->DebugName[0] = 0; + size = node->Size; + node->Allocated = false; + if (prev_node != NodeList.EndOfList() && prev_node->IsFree()) { + node->Address = address - prev_node->Size; + node->Size = size + prev_node->Size; + NodeList.Remove(prev_node); + RemoveNode(prev_node); + } + + TSMemoryNode *next_node = node->GetNext(); + if (next_node != NodeList.EndOfList() && next_node->IsFree()) { + node->Size += next_node->Size; + NodeList.Remove(next_node); + RemoveNode(next_node); + } + + AmountFree += size; + if (node->Size > LargestFree) { + LargestFree = node->Size; + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceFreePacket packet; + bMemoryTraceFreePacket *packet_ptr = &packet; + memset(packet_ptr, 0, sizeof(*packet_ptr)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = address; + packet.Size = size; + bFunkCallASync("CODEINE", 0x1b, packet_ptr, sizeof(*packet_ptr)); + } + return; + } + } +} + +TSMemoryNode *TSMemoryPool::GetNextNode(bool start_from_top, TSMemoryNode *node) { + if (start_from_top) { + if (node) { + node = node->GetNext(); + } else { + node = NodeList.GetHead(); + } + } else { + if (node) { + node = node->GetPrev(); + } else { + node = NodeList.GetTail(); + } + } + + if (node == NodeList.EndOfList()) { + return 0; + } + return node; +} + +TSMemoryNode *TSMemoryPool::GetNextFreeNode(bool start_from_top, TSMemoryNode *node) { + while ((node = GetNextNode(start_from_top, node)) != 0) { + if (!node->Allocated) { + return node; + } + } + return 0; +} + +TSMemoryNode *TSMemoryPool::GetNextAllocatedNode(bool start_from_top, TSMemoryNode *node) { + while ((node = GetNextNode(start_from_top, node)) != 0) { + if (node->Allocated) { + return node; + } + } + return 0; +} + +inline TSMemoryNode *TSMemoryPool::GetFirstAllocatedNode(bool start_from_top) { + return GetNextAllocatedNode(start_from_top, 0); +} + +void TSMemoryPool::DebugPrint() { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + const char *name = node->DebugName; + node->IsAllocated(); + (void)name; + } +} + +unsigned int TSMemoryPool::GetPoolChecksum() { + return 0; +} + +void *TrackStreamer::AllocateMemory(TrackStreamingSection *section, int allocation_params) { + void *buf = bMalloc(section->Size, section->SectionName, 0, allocation_params | 0x2007); + if (!buf) { + bBreak(); + } + return buf; +} + +bool TrackStreamer::WillUnloadBlock(TrackStreamingSection *section) { + unsigned int frame = section->UnactivatedFrameCount; + if ((frame != 0) && (frame == eFrameCounter)) { + if (LastWaitUntilRenderingDoneFrameCount != frame) { + return true; + } + } + return false; +} + +void TrackStreamer::UnloadSection(TrackStreamingSection *section) { + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + + if (section->Status == TrackStreamingSection::LOADED) { + if (WillUnloadBlock(section)) { + WaitForFrameBufferSwapDisabled = 1; + eWaitUntilRenderingDone(); + WaitForFrameBufferSwapDisabled = 0; + LastWaitUntilRenderingDoneFrameCount = eFrameCounter; + } + + section->UnactivatedFrameCount = 0; + bFree(section->pMemory); + section->LoadedTime = 0; + section->pMemory = 0; + section->Status = TrackStreamingSection::UNLOADED; + NumSectionsLoaded -= 1; + } +} + +int TrackStreamer::UnloadLeastRecentlyUsedSection() { + TrackStreamingSection *best_section = 0; + + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::LOADED && !section->CurrentlyVisible && + (!best_section || section->LastNeededTimestamp < best_section->LastNeededTimestamp)) { + best_section = section; + } + } + + if (!best_section) { + return 0; + } + + UnloadSection(best_section); + return best_section->LoadedSize; +} + +void TrackStreamer::JettisonSection(TrackStreamingSection *section) { + AmountJettisoned += section->Size; + JettisonedSections[NumJettisonedSections] = section; + NumJettisonedSections += 1; + + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + if (section->Status == TrackStreamingSection::LOADED) { + UnloadSection(section); + } + + section->CurrentlyVisible = false; + + int index = 0; + while (CurrentStreamingSections[index] != section) { + index += 1; + } + + while (index < NumCurrentStreamingSections - 1) { + CurrentStreamingSections[index] = CurrentStreamingSections[index + 1]; + index += 1; + } + + NumCurrentStreamingSections -= 1; +} + +bool TrackStreamer::JettisonLeastImportantSection() { + TrackStreamingSection *best_section = ChooseSectionToJettison(); + if (best_section) { + JettisonSection(best_section); + return true; + } + return false; +} + +int TrackStreamer::AllocateSectionMemory(int *ptotal_needing_allocation) { + ProfileNode profile_node("TODO", 0); + int out_of_memory_size = 0; + int total_needing_allocation = 0; + int num_sections_allocated = 0; + + if (LoadingPhase == ALLOCATING_REGULAR_SECTIONS && pDiscBundleSections < pLastDiscBundleSection) { + for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; + disc_bundle = disc_bundle->GetMemoryImageNext()) { + int i = 0; + if (disc_bundle->NumMembers > 0) { + do { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + if (!section->CurrentlyVisible || section->Status != TrackStreamingSection::UNLOADED) { + break; + } + i += 1; + } while (i < disc_bundle->NumMembers); + } + + if (i == disc_bundle->NumMembers) { + if (disc_bundle->FileSize <= pMemoryPool->GetLargestFreeBlock()) { + unsigned char *pmemory = + static_cast(pMemoryPool->Malloc(disc_bundle->FileSize, disc_bundle->SectionName, true, false, 0)); + pMemoryPool->Free(pmemory); + + for (i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + void *realloc_mem = pmemory + member->FileOffset * 0x80; + + num_sections_allocated += 1; + section->pDiscBundle = disc_bundle; + section->Status = TrackStreamingSection::ALLOCATED; + section->pMemory = realloc_mem; + pMemoryPool->Malloc(section->Size, disc_bundle->SectionName, false, false, reinterpret_cast(realloc_mem)); + } + + total_needing_allocation += disc_bundle->FileSize; + } + } + } + } + + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->Status != TrackStreamingSection::UNLOADED) { + continue; + } + + if (section->SectionNumber != GetScenerySectionNumber('Y', 0) && section->SectionNumber != GetScenerySectionNumber('X', 0) && + section->SectionNumber != GetScenerySectionNumber('W', 0) && section->SectionNumber != GetScenerySectionNumber('U', 0) && + section->SectionNumber != GetScenerySectionNumber('Z', 0)) { + if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS) { + if (!IsTextureSection(section->SectionNumber)) { + continue; + } + } + + if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS) { + if (!IsLibrarySection(section->SectionNumber)) { + continue; + } + } + } + + total_needing_allocation += section->Size; + if (bLargestMalloc(7) < section->Size) { + out_of_memory_size += section->Size; + NumSectionsOutOfMemory += 1; + } else { + num_sections_allocated += 1; + section->pMemory = AllocateMemory(section, 0x80); + section->Status = TrackStreamingSection::ALLOCATED; + if (num_sections_allocated > 99999) { + CurrentZoneAllocatedButIncomplete = true; + return out_of_memory_size; + } + } + } + + CurrentZoneAllocatedButIncomplete = false; + *ptotal_needing_allocation = total_needing_allocation; + return out_of_memory_size; +} + +TrackStreamingSection *TrackStreamer::FindSection(int section_number) { + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->SectionNumber == static_cast(section_number)) { + return section; + } + } + return 0; +} + +TrackStreamingSection *TrackStreamer::FindSectionByAddress(int address) { + int n; + + { + TrackStreamingSection *section; + for (n = 0; n < NumCurrentStreamingSections; n++) { + section = CurrentStreamingSections[n]; + if (section->pMemory == reinterpret_cast(address)) { + return section; + } + } + } + + { + TrackStreamingSection *section; + for (n = 0; n < NumTrackStreamingSections; n++) { + section = &pTrackStreamingSections[n]; + if (section->pMemory == reinterpret_cast(address)) { + return section; + } + } + } + + return 0; +} + +int TrackStreamer::BuildHoleMovements(HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, + int max_amount_to_move) { + ProfileNode profile_node("TODO", 0); + int ticks = bGetTicker(); + unsigned int checksum = pMemoryPool->GetPoolChecksum(); + bool failed; + int num_movements; + int amount_moved; + int total_needing_allocation; + + pMemoryPool->EnableTracing(false); + total_needing_allocation = -1; + failed = false; + num_movements = 0; + amount_moved = 0; + while (true) { + if (largest_free >= 0) { + if (pMemoryPool->GetLargestFreeBlock() >= largest_free) { + break; + } + } else { + int out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + FreeSectionMemory(); + if (out_of_memory_size == 0) { + break; + } + } + + if (filler_method != 0) { + if (pMemoryPool->GetAmountFree() == pMemoryPool->GetLargestFreeBlock()) { + break; + } + } + + if (num_movements == max_movements) { + break; + } + + HoleMovement *movement = &hole_movements[num_movements]; + movement->Address = 0; + + if (filler_method == 2 || filler_method == 0 || filler_method == 3) { + bool start_from_top = filler_method == 3; + TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); + TSMemoryNode *node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); + if (filler_method == 0 && !node) { + break; + } + + if (node && free_node) { + movement->Size = node->Size; + movement->Address = node->Address; + movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); + if (filler_method == 0 && !FindSectionByAddress(movement->Address)) { + break; + } + } + } else if (filler_method == 4 || filler_method == 5) { + bool start_from_top = filler_method == 5; + TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); + int best_hole_size = 0; + bool first = true; + + for (TSMemoryNode *next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); next_node; + next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, next_node)) { + TSMemoryNode *next_free = pMemoryPool->GetNextFreeNode(start_from_top, next_node); + if (!next_free) { + continue; + } + if (first || next_node->Size <= free_node->Size) { + TSMemoryNode *node1 = pMemoryPool->GetNextNode(!start_from_top, next_node); + TSMemoryNode *node2 = pMemoryPool->GetNextNode(start_from_top, next_node); + int hole_size = next_node->Size; + if (node1 && node1->IsFree()) { + hole_size += node1->Size; + } + if (node2 && node2->IsFree()) { + hole_size += node2->Size; + } + if (hole_size > best_hole_size) { + best_hole_size = hole_size; + movement->Size = next_node->Size; + movement->Address = next_node->Address; + movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); + first = false; + } + } + } + } else if (filler_method == 1) { + bool done = false; + bool found_one = false; + bool found_big_enough = false; + TSMemoryNode *largest_allocated = 0; + int current_best = 0; + int current_best_middle_memory = 0x3E8000; + int best_address = 0; + bool first_pass = true; + TSMemoryNode *top_free_top = 0; + + do { + if (first_pass) { + first_pass = false; + top_free_top = pMemoryPool->GetFirstNode(true); + } else { + top_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); + } + + if (!top_free_top) { + done = true; + } else { + int top_free_memory = top_free_top->Size; + TSMemoryNode *bottom_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); + if (!bottom_free_top) { + done = true; + } else { + TSMemoryNode *top_allocated = pMemoryPool->GetNextNode(true, top_free_top); + pMemoryPool->GetNextNode(false, bottom_free_top); + + int middle_allocated_memory = top_allocated->Size; + int total_free_memory = top_free_memory + bottom_free_top->Size; + int size_checking[32]; + int i = 0; + do { + size_checking[i] = 0; + i += 1; + } while (i < 32); + + size_checking[0] = top_allocated->Size; + + TSMemoryNode *largest_allocated_here = top_allocated; + TSMemoryNode *cursor = top_allocated; + int found_nodes = 1; + while ((cursor = pMemoryPool->GetNextNode(true, top_allocated)) != bottom_free_top && top_allocated && found_nodes < 32) { + top_allocated = pMemoryPool->GetNextNode(true, top_allocated); + if (top_allocated) { + size_checking[found_nodes] = top_allocated->Size; + middle_allocated_memory += top_allocated->Size; + found_nodes += 1; + if (top_allocated->Size > largest_allocated_here->Size) { + largest_allocated_here = top_allocated; + } + } + } + + int free_gap = total_free_memory - middle_allocated_memory; + if ((!found_big_enough && current_best < free_gap) || + (found_big_enough && total_free_memory + middle_allocated_memory >= total_needing_allocation && + middle_allocated_memory < current_best_middle_memory)) { + std::sort(size_checking, size_checking + found_nodes); + int evaluated_best_address = 0; + bool largest_flag = false; + int nodes_to_move = 0; + int position = 0; + + TSMemoryNode *evaluated_top_free = pMemoryPool->GetFirstFreeNode(true); + while (found_nodes > nodes_to_move && evaluated_top_free) { + bool skip_flag = false; + int target_index = found_nodes - nodes_to_move - 1; + for (int i = 0; i < found_nodes; i++) { + if (size_checking[target_index] == position) { + skip_flag = true; + } + } + if (evaluated_top_free == top_free_top || evaluated_top_free == bottom_free_top) { + skip_flag = true; + } + if (!skip_flag && evaluated_top_free->Size >= size_checking[target_index]) { + size_checking[target_index] = position; + nodes_to_move += 1; + if (!largest_flag) { + evaluated_best_address = evaluated_top_free->Address; + largest_flag = true; + } + evaluated_top_free = pMemoryPool->GetNextFreeNode(true, 0); + } + evaluated_top_free = pMemoryPool->GetNextFreeNode(true, evaluated_top_free); + position += 1; + } + + if (nodes_to_move >= found_nodes) { + current_best = free_gap; + best_address = evaluated_best_address; + found_one = true; + largest_allocated = largest_allocated_here; + if (total_free_memory + middle_allocated_memory >= total_needing_allocation) { + found_big_enough = true; + current_best_middle_memory = middle_allocated_memory; + } + } + } + } + } + } while (!done); + + if (found_one && largest_allocated && FindSectionByAddress(largest_allocated->Address)) { + movement->Size = largest_allocated->Size; + movement->Address = largest_allocated->Address; + movement->NewAddress = best_address; + } + } + + if (movement->Address == 0) { + failed = true; + break; + } + + num_movements += 1; + movement->Checksum = pMemoryPool->GetPoolChecksum(); + pMemoryPool->Free(reinterpret_cast(movement->Address)); + pMemoryPool->Malloc(movement->Size, "HoleMovement", false, false, movement->NewAddress); + amount_moved += movement->Size; + if (max_amount_to_move < amount_moved) { + failed = true; + break; + } + } + + for (int n = num_movements - 1; n >= 0; n--) { + HoleMovement *movement = &hole_movements[n]; + pMemoryPool->Free(reinterpret_cast(movement->NewAddress)); + TrackStreamingSection *section = FindSectionByAddress(movement->Address); + char *debug_name; + if (section) { + debug_name = section->SectionName; + } else { + debug_name = "UndoHoleMovement"; + } + pMemoryPool->Malloc(movement->Size, debug_name, false, false, movement->Address); + } + + pMemoryPool->EnableTracing(true); + if (pamount_moved) { + *pamount_moved = amount_moved; + } + if (failed) { + return -1; + } + return num_movements; +} + +TrackStreamer::TrackStreamer() { + pTrackStreamingSections = 0; + NumTrackStreamingSections = 0; + pDiscBundleSections = 0; + pLastDiscBundleSection = 0; + NumSectionsLoaded = 0; + NumSectionsLoading = 0; + NumSectionsActivated = 0; + NumSectionsOutOfMemory = 0; + NumSectionsMoved = 0; + bMemSet(StreamFilenames, 0, sizeof(StreamFilenames)); + SplitScreen = false; + PermFileLoading = false; + PermFilename = 0; + PermFileChunks = 0; + PermFileSize = 0; + NumBarriers = 0; + pBarriers = 0; + NumCurrentStreamingSections = 0; + NumHibernatingSections = 0; + CurrentZoneNeedsRefreshing = false; + ZoneSwitchingDisabled = false; + LastWaitUntilRenderingDoneFrameCount = 0; + LastPrintedFrameCount = 0; + SkipNextHandleLoad = false; + + ClearCurrentZones(); + ClearStreamingPositions(); + + pMemoryPoolMem = 0; + MemoryPoolSize = 0; + UserMemoryAllocationSize = 0; + pMemoryPool = 0; + + CurrentVisibleSectionTable.Init(CurrentVisibleSectionTableMem.Bits, 0xAF0); + CurrentVisibleSectionTable.ClearTable(); + bMemSet(KeepSectionTable, 0, sizeof(KeepSectionTable)); + pCallback = 0; + CallbackParam = 0; + MakeSpaceInPoolCallback = 0; + MakeSpaceInPoolCallbackParam = 0; + MakeSpaceInPoolSize = 0; +} + +int LoaderTrackStreamer(bChunk *chunk) { + return TheTrackStreamer.Loader(chunk); +} + +int UnloaderTrackStreamer(bChunk *chunk) { + return TheTrackStreamer.Unloader(chunk); +} + +void RefreshTrackStreamer() { + TheTrackStreamer.RefreshLoading(); +} + +void TrackStreamer::InitMemoryPool(int size) { + MemoryPoolSize = size; +#ifdef MILESTONE_OPT + pMemoryPoolMem = bMalloc(size, "Track Streaming", 0, 0x2000); +#else + pMemoryPoolMem = bMalloc(size, 0x2000); +#endif + pMemoryPool = new TSMemoryPool(reinterpret_cast(pMemoryPoolMem), MemoryPoolSize, "Track Streaming", 7); +} + +int TrackStreamer::GetMemoryPoolSize() { + if (pMemoryPool->IsUpdated()) { + UserMemoryAllocationSize = CountUserAllocations(0); + } + return MemoryPoolSize - UserMemoryAllocationSize; +} + +int TrackStreamer::CountUserAllocations(const char **pfragmented_user_allocation) { + int num_fragmented_user_allocations; + + if (pfragmented_user_allocation) { + *pfragmented_user_allocation = 0; + } + + num_fragmented_user_allocations = 0; + int user_allocation_size = 0; + bool start_from_top = false; + TSMemoryNode *node = pMemoryPool->GetFirstAllocatedNode(start_from_top); + while (node) { + TrackStreamingSection *section = FindSectionByAddress(node->Address); + if (!section) { + user_allocation_size += node->Size; + if (pMemoryPool->GetNextFreeNode(start_from_top, node) && pMemoryPool->GetNextFreeNode(!start_from_top, node) && + pfragmented_user_allocation) { + *pfragmented_user_allocation = node->DebugName; + num_fragmented_user_allocations += 1; + } + } + + node = pMemoryPool->GetNextAllocatedNode(start_from_top, node); + } + + (void)num_fragmented_user_allocations; + return user_allocation_size; +} + +int TrackStreamer::DoHoleFilling(int largest_free) { + ProfileNode profile_node("TODO", 0); + const char *fragmented_user_allocation; + HoleMovement hole_movement_table[128]; + + CountUserAllocations(&fragmented_user_allocation); + if (fragmented_user_allocation) { + pMemoryPool->DebugPrint(); + return 0; + } + + int best_method = -1; + int forced_hole_filler_method = ForceHoleFillerMethod; + if (forced_hole_filler_method >= 0) { + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, forced_hole_filler_method, largest_free, 0, 0x7FFFFFFF); + if (num_hole_movements > 0) { + best_method = forced_hole_filler_method; + } + } else { + int best_amount_moved = 0x7FFFFFFF; + for (int filler_method = 1; filler_method < 6; filler_method++) { + int amount_moved; + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, filler_method, largest_free, &amount_moved, best_amount_moved); + if (num_hole_movements > 0 && amount_moved < best_amount_moved) { + best_method = filler_method; + best_amount_moved = amount_moved; + } + } + } + + if (best_method < 0) { + return 0; + } + + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, best_method, largest_free, 0, 0x7FFFFFFF); + for (int n = 0; n < num_hole_movements; n++) { + ProfileNode profile_node("TODO", 0); + HoleMovement *movement = &hole_movement_table[n]; + TrackStreamingSection *section = FindSectionByAddress(movement->Address); + if (LastWaitUntilRenderingDoneFrameCount != eGetFrameCounter()) { + int start_ticks = bGetTicker(); + DisableWaitForFrameBufferSwap(); + eWaitUntilRenderingDone(); + LastWaitUntilRenderingDoneFrameCount = eGetFrameCounter(); + EnableWaitForFrameBufferSwap(); + float time = bGetTickerDifference(start_ticks); + (void)time; + } + + int start_ticks = bGetTicker(); + void *new_memory = reinterpret_cast(movement->NewAddress); + pMemoryPool->Free(reinterpret_cast(movement->Address)); + pMemoryPool->Malloc(movement->Size, section->SectionName, false, false, movement->NewAddress); + if (section->Status == TrackStreamingSection::ACTIVATED) { + eAllowDuplicateSolids(true); + SetDuplicateTextureWarning(false); + MoveChunks(reinterpret_cast(new_memory), reinterpret_cast(section->pMemory), section->LoadedSize, + section->SectionName); +#ifdef EA_PLATFORM_GAMECUBE + DCStoreRangeNoSync(new_memory, section->LoadedSize); +#endif + eAllowDuplicateSolids(false); + SetDuplicateTextureWarning(true); + } else { + eWaitUntilRenderingDone(); + bOverlappedMemCpy(new_memory, section->pMemory, section->LoadedSize); + } + section->pMemory = new_memory; + float move_time = bGetTickerDifference(start_ticks); + (void)move_time; + NumSectionsMoved += 1; +#ifdef EA_PLATFORM_GAMECUBE + PPCSync(); +#endif + } + + return 1; +} + +void TrackStreamer::ClearCurrentZones() { + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->AmountLoaded = 0; + position_entry->CurrentZone = 0; + position_entry->BeginLoadingTime = 0.0f; + position_entry->BeginLoadingPosition.x = 0.0f; + position_entry->BeginLoadingPosition.y = 0.0f; + position_entry->NumSectionsToLoad = 0; + position_entry->NumSectionsLoaded = 0; + position_entry->AmountToLoad = 0; + } + + CurrentZoneFarLoad = true; + StartLoadingTime = 0.0f; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + CurrentZoneNonReplayLoad = false; + LoadingPhase = LOADING_IDLE; + LoadingBacklog = 0.0f; + CurrentZoneName[0] = 0; + NumJettisonedSections = 0; + MemorySafetyMargin = 0; + AmountJettisoned = 0; + bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); + RemoveCurrentStreamingSections(); +} + +void TrackStreamer::RemoveCurrentStreamingSections() { + for (int i = 0; i < NumCurrentStreamingSections; i++) { + CurrentStreamingSections[i]->CurrentlyVisible = 0; + } + + NumCurrentStreamingSections = 0; + bBitTableLayout_TrackStreamer *layout = reinterpret_cast(&CurrentVisibleSectionTable); + bMemSet(layout->Bits, 0, layout->NumBits >> 3); +} + +void TrackStreamer::AddCurrentStreamingSections(short *section_numbers, int num_sections, int position_number) { + int i = 0; + if (i < num_sections) { + StreamingPositionEntry *streaming_position = &StreamingPositionEntries[position_number]; + unsigned int position_bit = 1 << position_number; + do { + short §ion_number = section_numbers[i]; + CurrentVisibleSectionTable.Set(section_number); + if (SplitScreen) { + section_number = static_cast(Get2PlayerSectionNumber(section_number)); + } + + TrackStreamingSection *section = FindSection(section_number); + if (!section) { + continue; + } + + section->LastNeededTimestamp = RealTimeFrames; + if (!section->CurrentlyVisible) { + CurrentStreamingSections[NumCurrentStreamingSections++] = section; + } + + if ((((static_cast(section->CurrentlyVisible) >> position_number) ^ 1U) & 1) != 0) { + section->CurrentlyVisible |= static_cast(position_bit); + if (section->Status < TrackStreamingSection::LOADED) { + streaming_position->NumSectionsToLoad += 1; + streaming_position->AmountToLoad += section->Size; + } + } + i += 1; + } while (i < num_sections); + } +} + +void TrackStreamer::DetermineStreamingSections() { + const int max_sections_to_load = 0x180; + short sections_to_load[384]; + int num_sections_to_load = 3; + unsigned short section_number; + + RemoveCurrentStreamingSections(); + sections_to_load[0] = GetScenerySectionNumber_TrackStreamer('Y', 0); + sections_to_load[1] = GetScenerySectionNumber_TrackStreamer('X', 0); + sections_to_load[2] = GetScenerySectionNumber_TrackStreamer('Z', 0); + + { + short *sections_to_load_ptr = sections_to_load; + if (SeeulatorToolActive && ScenerySectionToBlink != 0) { + num_sections_to_load = 4; + sections_to_load_ptr[3] = static_cast(ScenerySectionToBlink); + } + + for (int n = 0; n < 4; n++) { + section_number = KeepSectionTable[n]; + if (section_number != 0) { + sections_to_load_ptr[num_sections_to_load] = section_number; + num_sections_to_load += 1; + } + } + + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 0); + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 1); + int position_number = 0; + do { + { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (position_entry->CurrentZone > 0) { + { + LoadingSection *loading_section = TheVisibleSectionManager.FindLoadingSection(position_entry->CurrentZone); + if (!loading_section) { + { + DrivableScenerySection *drivable_section = TheVisibleSectionManager.FindDrivableSection(position_entry->CurrentZone); + num_sections_to_load = 0; + for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { + { + int section_number = drivable_section->GetVisibleSection(i); + sections_to_load_ptr[num_sections_to_load] = section_number; + num_sections_to_load += 1; + } + } + } + } else { + num_sections_to_load = + TheVisibleSectionManager.GetSectionsToLoad(loading_section, sections_to_load_ptr, max_sections_to_load); + } + } + + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, position_number); + } + } + position_number += 1; + } while (position_number < 2); + } +} + +void TrackStreamer::InitRegion(const char *region_stream_filename, bool split_screen) { + bool flush_hibernating_sections = false; + + if (SplitScreen != split_screen) { + SplitScreen = split_screen; + flush_hibernating_sections = true; + } + if (!bStrEqual(StreamFilenames[1], region_stream_filename)) { + flush_hibernating_sections = true; + bStrCpy(StreamFilenames[1], region_stream_filename); + } + if (flush_hibernating_sections) { + FlushHibernatingSections(); + } + if (PermFileLoading) { + BlockWhileQueuedFileBusy(); + } + + ClearCurrentZones(); + ClearStreamingPositions(); + + { + int position_number = 0; + do { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + + position_entry->AudioBlockingPosition.x = 0.0f; + position_entry->PredictedZone = 0; + position_entry->PredictedZoneValidTime = 0; + position_entry->AudioReading = false; + position_entry->AudioReadingTime = 0.0f; + position_entry->AudioReadingPosition.x = 0.0f; + position_entry->AudioReadingPosition.y = 0.0f; + position_entry->AudioBlocking = false; + position_entry->AudioBlockingTime = 0.0f; + position_entry->AudioBlockingPosition.y = 0.0f; + position_number += 1; + } while (position_number < 2); + } + + int n = 0; + while (n < NumTrackStreamingSections) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + int boundary_section_number = GetBoundarySectionNumber(static_cast(section->SectionNumber), bGetPlatformName()); + VisibleSectionBoundary *boundary = TheVisibleSectionManager.FindBoundary(boundary_section_number); + + section->pBoundary = boundary; + n += 1; + } + + EmptyCaffeineLayers(); +} + +void TrackStreamer::PlotLoadingMarker(StreamingPositionEntry *streaming_position) { + char stack[0x20]; + (void)stack; +} + +void TrackStreamer::SwitchZones(short *current_zones) { + StartLoadingTime = GetDebugRealTime(); + CurrentZoneNeedsRefreshing = false; + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + int zone_number = current_zones[position_number]; + if (position_entry->CurrentZone != zone_number) { + PlotLoadingMarker(position_entry); + + VisibleSectionBoundary *boundary1 = TheVisibleSectionManager.FindBoundary(position_entry->CurrentZone); + VisibleSectionBoundary *boundary2 = TheVisibleSectionManager.FindBoundary(zone_number); + float best_distance = kMaxDistance_TrackStreamer; + if (boundary1 && boundary2) { + for (int n = 0; n < boundary1->GetNumPoints(); n++) { + float distance = boundary2->GetDistanceOutside(boundary1->GetPoint(n), kMaxDistance_TrackStreamer); + best_distance = bMin(best_distance, distance); + } + } + + if (kSwitchZoneFarLoadThreshold_TrackStreamer < best_distance) { + CurrentZoneFarLoad = true; + } + + position_entry->CurrentZone = zone_number; + position_entry->BeginLoadingPosition = position_entry->Position; + position_entry->BeginLoadingTime = GetDebugRealTime(); + position_entry->NumSectionsToLoad = 0; + position_entry->NumSectionsLoaded = 0; + position_entry->AmountToLoad = 0; + position_entry->AmountLoaded = 0; + } + + if (position_number == 0) { + GetScenerySectionName(CurrentZoneName, zone_number); + } else if (zone_number > 0) { + bSPrintf(CurrentZoneName, "%s - %s", CurrentZoneName, GetScenerySectionName(zone_number)); + } + } + + int num_sections_unactivated = 0; + DetermineStreamingSections(); + PostLoadFixupDisabled = true; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ACTIVATED && !section->CurrentlyVisible) { + if (!IsTextureSection(section->SectionNumber) && !IsLibrarySection(section->SectionNumber)) { + UnactivateSection(section); + num_sections_unactivated += 1; + } + } + } + PostLoadFixupDisabled = false; + + if (num_sections_unactivated > 0) { + PostLoadFixup(); + SkipNextHandleLoad = true; + } + + FreeSectionMemory(); + SetLoadingPhase(ALLOCATING_TEXTURE_SECTIONS); + NumJettisonedSections = 0; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + MemorySafetyMargin = 0; + AmountJettisoned = 0; + bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); + AssignLoadingPriority(); + CalculateLoadingBacklog(); +} + +int TrackStreamer::Loader(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + if (chunk_id == 0x34110) { + pTrackStreamingSections = reinterpret_cast(chunk->GetData()); + NumTrackStreamingSections = chunk->Size / sizeof(TrackStreamingSection); + for (int i = 0; i < NumTrackStreamingSections; i++) { + TrackStreamingSection *section = &pTrackStreamingSections[i]; + bEndianSwap16(§ion->SectionNumber); + bEndianSwap32(§ion->FileType); + bEndianSwap32(§ion->Status); + bEndianSwap32(§ion->FileOffset); + bEndianSwap32(§ion->Size); + bEndianSwap32(§ion->CompressedSize); + bEndianSwap32(§ion->PermSize); + bEndianSwap32(§ion->SectionPriority); + bPlatEndianSwap(§ion->Centre); + bEndianSwap32(§ion->Radius); + bEndianSwap32(§ion->Checksum); + } + + for (int i = 0; i < NumHibernatingSections; i++) { + TrackStreamingSection *src = &HibernatingSections[i]; + TrackStreamingSection *section = FindSection(src->SectionNumber); + bMemCpy(section, src, sizeof(TrackStreamingSection)); + NumSectionsLoaded += 1; + ActivateSection(section); + int current_streaming_section = NumCurrentStreamingSections; + CurrentStreamingSections[current_streaming_section] = section; + NumCurrentStreamingSections = current_streaming_section + 1; + } + + NumHibernatingSections = 0; + return 1; + } else if (chunk_id == 0x34113) { + pDiscBundleSections = reinterpret_cast(chunk->GetData()); + pLastDiscBundleSection = reinterpret_cast(reinterpret_cast(pDiscBundleSections) + chunk->Size); + for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; + disc_bundle = reinterpret_cast(reinterpret_cast(disc_bundle) + + (disc_bundle->NumMembers * sizeof(DiscBundleSectionMember) + 0x14))) { + bEndianSwap32(&disc_bundle->FileOffset); + bEndianSwap32(&disc_bundle->FileSize); + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + bEndianSwap16(&member->SectionNumber); + bEndianSwap16(&member->FileOffset); + member->pSection = FindSection(member->SectionNumber); + } + } + return 1; + } else if (chunk_id == 0x34111) { + pInfo = reinterpret_cast(chunk->GetData()); + for (int i = 0; i < 2; i++) { + bEndianSwap32(i + pInfo->FileSize); + } + return 1; + } else if (chunk_id == 0x34112) { + pBarriers = reinterpret_cast(chunk->GetData()); + NumBarriers = chunk->Size / sizeof(TrackStreamingBarrier); + for (int i = 0; i < NumBarriers; i++) { + TrackStreamingBarrier *barrier = &pBarriers[i]; + bPlatEndianSwap(&barrier->Points[0]); + bPlatEndianSwap(&barrier->Points[1]); + } + return 1; + } else { + return 0; + } +} + +int TrackStreamer::Unloader(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + if (chunk_id == 0x34110) { + UnloadEverything(); + pTrackStreamingSections = 0; + NumTrackStreamingSections = 0; + return 1; + } + + if (chunk_id == 0x34113) { + pDiscBundleSections = 0; + pLastDiscBundleSection = 0; + return 1; + } + + if (chunk_id == 0x34111) { + pInfo = 0; + return 1; + } + + if (chunk_id == 0x34112) { + pBarriers = 0; + NumBarriers = 0; + return 1; + } + + return 0; +} + +void TrackStreamer::HibernateStreamingSections() { + int sections_to_hibernate[5]; + int n; + int section_number; + TrackStreamingSection *section; + TrackStreamingSection *hibernating_section; + + (void)sections_to_hibernate; + (void)n; + (void)section_number; + (void)section; + (void)hibernating_section; + return; +} + +void TrackStreamer::FlushHibernatingSections() { + for (int n = 0; n < NumHibernatingSections; n++) { + TrackStreamingSection *section = &HibernatingSections[n]; + bFree(section->pMemory); + } + NumHibernatingSections = 0; +} + +bool TrackStreamer::NeedsGameStateActivation(TrackStreamingSection *section) { + return false; + + if (IsRegularScenerySection(section->SectionNumber) && IsLODScenerySectionNumber(section->SectionNumber)) { + return true; + } +} + +void TrackStreamer::FreeSectionMemory() { + NumSectionsOutOfMemory = 0; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ALLOCATED) { + bFree(section->pMemory); + section->pDiscBundle = 0; + section->pMemory = 0; + section->Status = TrackStreamingSection::UNLOADED; + } + } +} + +int TrackStreamer::GetSectionToActivate(int activation_delay) { + if (NumSectionsActivated < NumCurrentStreamingSections) { + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + if (section->Status == TrackStreamingSection::LOADED && TheTrackStreamer.NeedsGameStateActivation(section) && + RealTimeFrames - section->LoadedTime >= activation_delay) { + return section->SectionNumber; + } + } + } + + return 0; +} + +int TrackStreamer::GetCombinedSectionNumber(int section_number) { + bool use_combined_section = false; + if ((static_cast(section_number / 100 - 1) & 0xFF) < 0x14) { + int subsection_number = section_number % 100; + use_combined_section = subsection_number > 0 && subsection_number < ScenerySectionLODOffset; + } + + if (use_combined_section) { + int combined_section_number = section_number + ScenerySectionLODOffset; + TrackStreamingSection *section = FindSection(section_number); + if (!section) { + section = FindSection(combined_section_number); + if (section) { + return combined_section_number; + } + } + } + + return section_number; +} + +void TrackStreamer::HandleSectionActivation() { + ProfileNode profile_node("TODO", 0); + int activation_delay; + short section_to_activate = static_cast(GetSectionToActivate(0)); + (void)activation_delay; + if (section_to_activate != 0) { + TrackStreamingSection *section = FindSection(section_to_activate); + if (section->Status != TrackStreamingSection::ACTIVATED) { + if (section->Status != TrackStreamingSection::LOADED) { + if (!section->CurrentlyVisible) { + return; + } + + do { + HandleLoading(); + ServiceResourceLoading(); + } while (section->Status != TrackStreamingSection::LOADED); + } + ActivateSection(section); + } + } +} + +void TrackStreamer::UnloadEverything() { + while (NumSectionsLoading != 0) { + ServiceResourceLoading(); + } + + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (static_cast(section->Status - TrackStreamingSection::LOADED) < 2U) { + UnloadSection(section); + } + } + + FreeSectionMemory(); + ClearCurrentZones(); +} + +void TrackStreamer::ActivateSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + int allocation_params = 0x2087; + NumSectionsActivated += 1; + eAllowDuplicateSolids(true); + SetDuplicateTextureWarning(false); + + bChunk *chunks = reinterpret_cast(section->pMemory); + int sizeof_chunks = section->LoadedSize; + LoadTempPermChunks(&chunks, &sizeof_chunks, allocation_params, section->SectionName); + + section->pMemory = chunks; + section->LoadedSize = sizeof_chunks; + section->Status = TrackStreamingSection::ACTIVATED; + section->LoadedTime = 0; + eAllowDuplicateSolids(false); + SetDuplicateTextureWarning(true); +} + +void TrackStreamer::UnactivateSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + section->UnactivatedFrameCount = 0; + DisableWaitUntilRenderingDone(); + section->UnactivatedFrameCount = eGetFrameCounter(); + UnloadChunks(reinterpret_cast(section->pMemory), section->LoadedSize, section->SectionName); + EnableWaitUntilRenderingDone(); + NumSectionsActivated -= 1; + section->Status = TrackStreamingSection::LOADED; +} + +void TrackStreamer::LoadDiscBundle(DiscBundleSection *disc_bundle) { + void *memory = 0; + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + if (i == 0) { + memory = section->pMemory; + } + section->Status = TrackStreamingSection::LOADING; + } + + NumSectionsLoading += 1; + AddQueuedFile(memory, StreamFilenames[1], disc_bundle->FileOffset, disc_bundle->FileSize, DiscBundleLoadedCallback, + reinterpret_cast(disc_bundle), 0); +} + +void TrackStreamer::DiscBundleLoadedCallback(int param, int error_status) { + (void)error_status; + TheTrackStreamer.DiscBundleLoadedCallback(reinterpret_cast(param)); +} + +void TrackStreamer::DiscBundleLoadedCallback(DiscBundleSection *disc_bundle) { + NumSectionsLoading += -1 + disc_bundle->NumMembers; + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + section->pDiscBundle = 0; + SectionLoadedCallback(section); + } +} + +void TrackStreamer::LoadSection(TrackStreamingSection *section) { + NumSectionsLoading += 1; + section->Status = TrackStreamingSection::LOADING; + + if (section->CompressedSize == section->Size) { + AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, + reinterpret_cast(section), 0); + } else { + QueuedFileParams params; + params.BlockSize = 0x7ffffff; + params.Priority = QueuedFileDefaultPriority; + params.Compressed = false; + params.Compressed = true; + params.UncompressedSize = section->Size; + AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, + reinterpret_cast(section), ¶ms); + } +} + +void TrackStreamer::SectionLoadedCallback(int param, int error_status) { + (void)error_status; + TheTrackStreamer.SectionLoadedCallback(reinterpret_cast(param)); +} + +void TrackStreamer::SectionLoadedCallback(TrackStreamingSection *section) { + section->Status = TrackStreamingSection::LOADED; + section->LoadedSize = section->Size; + EndianSwapChunkHeadersRecursive(reinterpret_cast(section->pMemory), section->Size); + NumSectionsLoading -= 1; + NumSectionsLoaded += 1; + section->LoadedTime = RealTimeFrames; + + if (section->CurrentlyVisible && !NeedsGameStateActivation(section)) { + ActivateSection(section); + } + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (((section->CurrentlyVisible >> position_number) & 1) != 0) { + position_entry->NumSectionsLoaded += 1; + position_entry->AmountLoaded += section->Size; + } + } + + CalculateLoadingBacklog(); +#ifdef EA_PLATFORM_GAMECUBE + DCStoreRange(section->pMemory, section->LoadedSize); +#endif +} + +TrackStreamingSection *TrackStreamer::ChooseSectionToJettison() { + TrackStreamingSection *best_section = 0; + int best_discard_priority = 0; + static int last_jettison_print; + bool print_jettison_this_frame = false; + + last_jettison_print = RealLoopCounter; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + int discard_priority = 0; + TrackStreamingSection *section = CurrentStreamingSections[i]; + + if (IsTextureSection(section->SectionNumber) || IsLibrarySection(section->SectionNumber)) { + discard_priority = 2; + if (section->SectionNumber == GetScenerySectionNumber('Y', 0) || section->SectionNumber == GetScenerySectionNumber('W', 0) || + section->SectionNumber == GetScenerySectionNumber('X', 0)) { + discard_priority = 1; + } else if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS && IsTextureSection(section->SectionNumber) && + section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { + discard_priority = 10000; + } else if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS && IsLibrarySection(section->SectionNumber) && + section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { + discard_priority = 10000; + } + } else if (IsRegularScenerySection(section->SectionNumber)) { + int loading_priority = GetLoadingPriority(section, &StreamingPositionEntries[0], true); + if (SplitScreen) { + int loading_priority2 = GetLoadingPriority(section, &StreamingPositionEntries[1], true); + if (loading_priority2 < loading_priority) { + loading_priority = loading_priority2; + } + } + discard_priority = loading_priority * 10 + 100; + } + + if (discard_priority != 0) { + if (static_cast(section->Status - TrackStreamingSection::LOADED) > 1) { + discard_priority += 1; + } + } + if (discard_priority > best_discard_priority) { + best_section = section; + best_discard_priority = discard_priority; + } + } + + return best_section; +} + +void TrackStreamer::UnJettisonSections() { + for (int n = 0; n < NumJettisonedSections; n++) { + TrackStreamingSection *section = JettisonedSections[n]; + CurrentStreamingSections[NumCurrentStreamingSections++] = section; + section->CurrentlyVisible = true; + } + NumJettisonedSections = 0; + AmountJettisoned = 0; +} + +bool TrackStreamer::HandleMemoryAllocation() { + int out_of_memory_size; + int total_amount_unloaded = 0; + int total_amount_hole_filled = 0; + + { + int total_needing_allocation; + int amount_unloaded; + + do { + amount_unloaded = UnloadLeastRecentlyUsedSection(); + } while (amount_unloaded != 0); + + NumSectionsMoved = 0; + + do { + do { + FreeSectionMemory(); + out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + if (out_of_memory_size == 0) { + goto done; + } + if (total_amount_unloaded > 0x7FFFFFFF || total_amount_hole_filled > 0x200000) { + return false; + } + if (NumSectionsMoved > 15) { + return false; + } + + FreeSectionMemory(); + amount_unloaded = UnloadLeastRecentlyUsedSection(); + total_amount_unloaded += amount_unloaded; + } while (amount_unloaded > 0); + + { + int amount_hole_filled; + int threshold = total_needing_allocation + 0x4000; + + while (bCountFreeMemory(7) < threshold) { + CurrentZoneOutOfMemory = true; + if (!JettisonLeastImportantSection()) { + break; + } + AllocateSectionMemory(&total_needing_allocation); + FreeSectionMemory(); + threshold = total_needing_allocation + 0x4000; + } + + amount_hole_filled = DoHoleFilling(0); + total_amount_hole_filled += amount_hole_filled; + if (amount_hole_filled != 0) { + continue; + } + } + + CurrentZoneOutOfMemory = true; + } while (JettisonLeastImportantSection()); + + out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + } + +done: + MemorySafetyMargin = 0; + + { + int amount_jettisoned = AmountJettisoned; + int free_memory = bCountFreeMemory(7) - (out_of_memory_size + amount_jettisoned); + { + int n = 0; + int num_track_streaming_sections = NumTrackStreamingSections; + + if (num_track_streaming_sections > 0) { + do { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (!section->CurrentlyVisible && section->Status != TrackStreamingSection::UNLOADED) { + free_memory += section->PermSize; + } + n += 1; + } while (n < num_track_streaming_sections); + } + } + MemorySafetyMargin = free_memory; + } + + if (out_of_memory_size + AmountJettisoned != 0) { + { + int n = 0; + + if (NumJettisonedSections > 0) { + do { + n += 1; + } while (n < NumJettisonedSections); + } + } + + if (LoadingPhase == LOADING_REGULAR_SECTIONS) { + int i = 0; + + if (NumCurrentStreamingSections > 0) { + do { + TrackStreamingSection *section; + i += 1; + } while (i < NumCurrentStreamingSections); + } + } + } + + return true; +} + +void TrackStreamer::StartLoadingSections() { + bool something_to_load = true; + while (NumSectionsLoading < 2 && something_to_load) { + int best_priority = 0x7FFFFFFF; + TrackStreamingSection *best_section = 0; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->Status == TrackStreamingSection::ALLOCATED) { + int priority = section->LoadingPriority; + if (section->pDiscBundle) { + priority = -1; + } + if (priority < best_priority) { + best_priority = priority; + best_section = section; + } + } else if (section->Status == TrackStreamingSection::LOADED && !NeedsGameStateActivation(section)) { + TheTrackStreamer.ActivateSection(section); + } + } + + if (!best_section) { + something_to_load = false; + } else { + if (best_section->pDiscBundle) { + DiscBundleSection *disc_bundle = best_section->pDiscBundle; + LoadDiscBundle(disc_bundle); + } else { + LoadSection(best_section); + } + } + } +} + +void TrackStreamer::FinishedLoading() { + { + float load_time; + int position_number; + StreamingPositionEntry *position_entry; + (void)load_time; + (void)position_number; + (void)position_entry; + } + + LoadingPhase = LOADING_IDLE; + CurrentZoneNonReplayLoad = false; + CurrentZoneFarLoad = false; + NotifySkyLoader(); + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (position_entry->BeginLoadingTime != 0.0f) { + PlotLoadingMarker(position_entry); + } + position_entry->BeginLoadingTime = 0.0f; + } + + if (pCallback) { + SetDelayedResourceCallback(pCallback, CallbackParam); + pCallback = 0; + CallbackParam = 0; + } +} + +void TrackStreamer::EmptyCaffeineLayers() { + TrackStreamerRemoteCaffeinating = 0; +} + +void TrackStreamer::HandleLoading() { + if (SkipNextHandleLoad) { + SkipNextHandleLoad = false; + } else { + if (LoadingPhase != LOADING_IDLE) { + if ((LoadingPhase == LOADING_TEXTURE_SECTIONS || LoadingPhase == LOADING_GEOMETRY_SECTIONS || LoadingPhase == LOADING_REGULAR_SECTIONS) && + (StartLoadingSections(), NumSectionsLoading == 0)) { + if (CurrentZoneAllocatedButIncomplete) { + SetLoadingPhase(static_cast(LoadingPhase - 1)); + } else { + if (LoadingPhase == LOADING_REGULAR_SECTIONS) { + FinishedLoading(); + return; + } + SetLoadingPhase(static_cast(LoadingPhase + 1)); + } + } + + if ((LoadingPhase == ALLOCATING_TEXTURE_SECTIONS || LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS || + LoadingPhase == ALLOCATING_REGULAR_SECTIONS) && + NumSectionsLoading < 1) { + int num_sections_unactivated = 0; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ACTIVATED && !section->CurrentlyVisible) { + if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS && IsTextureSection(section->SectionNumber)) { + num_sections_unactivated += 1; + UnactivateSection(section); + } else if (LoadingPhase == ALLOCATING_REGULAR_SECTIONS && IsLibrarySection(section->SectionNumber)) { + num_sections_unactivated += 1; + UnactivateSection(section); + } + } + } + + if (num_sections_unactivated > 0) { + SkipNextHandleLoad = true; + } else { + PostLoadFixupDisabled = true; + int did_allocate = HandleMemoryAllocation(); + PostLoadFixupDisabled = false; + if (NumSectionsMoved > 0) { + PostLoadFixup(); + } + if (did_allocate != 0) { + SetLoadingPhase(static_cast(LoadingPhase + 1)); + if (LoadingPhase == LOADING_TEXTURE_SECTIONS || LoadingPhase == LOADING_GEOMETRY_SECTIONS) { + UnJettisonSections(); + } + HandleLoading(); + } + } + } + } + } +} + +int TrackStreamer::GetLoadingPriority(TrackStreamingSection *section, StreamingPositionEntry *position_entry, bool calculating_jettison) { + if (!section->pBoundary) { + return 0; + } + + float speed = bLength(position_entry->Velocity); + if (calculating_jettison) { + speed = 100.0f; + } + + if (speed < 1.0f) { + return 0; + } + + bVector2 predict_pos = position_entry->Position + position_entry->Velocity * 1.0f; + VisibleSectionBoundary *boundary = section->pBoundary; + float distance = boundary->GetDistanceOutside(&predict_pos, 999.0f); + + bVector2 direction; + if (calculating_jettison) { + bNormalize(&direction, &position_entry->Direction); + } else { + bNormalize(&direction, &position_entry->Velocity); + } + + bVector2 v = section->Centre - predict_pos; + v = bNormalize(v); + float dot = bDot(&direction, &v); + float speed_factor = bMin(speed * 0.016666668f, 1.0f); + float angle = bAngToDeg(bACos(dot)); + float angle_factor = bClamp(angle, 20.0f, 90.0f); + float adjusted_distance = distance * (1.0f - (90.0f - angle_factor) * 0.014285714f * speed_factor * 0.66999996f); + int priority = static_cast(adjusted_distance * 0.013333334f); + + return bClamp(priority, 0, 2); +} + +void TrackStreamer::AssignLoadingPriority() { + espEmptyLayer(0); + + { + int priority; + + { + char layer_name[32]; + + espEmptyLayer(layer_name); + } + } + + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + int best_priority = 99; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (((section->CurrentlyVisible >> position_number) & 1U) != 0) { + { + int priority = GetLoadingPriority(section, position_entry, false); + if (priority < best_priority) { + best_priority = priority; + } + } + } + } + + section->LoadingPriority = best_priority * 100000 + section->SectionPriority; + } +} + +void TrackStreamer::CalculateLoadingBacklog() { + float loading_backlog = 0.0f; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->CurrentlyVisible && section->Status != TrackStreamingSection::LOADED && section->Status != TrackStreamingSection::ACTIVATED) { + int rounded_size = section->Size; + if (rounded_size < 0) { + rounded_size += 0x3ff; + } + + float section_backlog = static_cast(rounded_size >> 10) * 0.0004f + 0.2f; + if (section->BaseLoadingPriority == 1) { + section_backlog *= 0.4f; + } + if (section->BaseLoadingPriority == 2) { + section_backlog *= 0.2f; + } + loading_backlog += section_backlog; + } + } + + LoadingBacklog = loading_backlog; +} + +bool TrackStreamer::AreAllSectionsActivated() { + bool all_sections_activated = false; + if (LoadingPhase == LOADING_IDLE) { + all_sections_activated = NumCurrentStreamingSections <= NumSectionsActivated + NumSectionsOutOfMemory; + } + return all_sections_activated; +} + +bool TrackStreamer::IsLoadingInProgress() { + bool loading_in_progress = !AreAllSectionsActivated(); + + if (!loading_in_progress && !AreAllSectionsActivated()) { + while (!AreAllSectionsActivated()) { + ServiceResourceLoading(); + ServiceNonGameState(); + } + } + + return loading_in_progress; +} + +bool TrackStreamer::CheckLoadingBar() { + ProfileNode profile_node("TODO", 0); + float closest_distance = kMaxDistance_TrackStreamer; + TrackStreamingSection *closest_section; + StreamingPositionEntry *closest_position_entry; + float closest_approach_speed; + bool need_loading_bar; + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + float speed; + float max_speed; + float prediction_scale_a = kPredictionScaleA_TrackStreamer; + float prediction_scale_b = kPredictionScaleB_TrackStreamer; + + if (!IsLoadingInProgress()) { + break; + } + + if (IsFarLoadingInProgress() || !position_entry->PositionSet || !position_entry->FollowingCar) { + break; + } + + speed = bLength(&position_entry->Velocity); + max_speed = MPH2MPS(kLoadingBarSpeedThreshold_TrackStreamer); + if (speed > max_speed) { + break; + } + + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + VisibleSectionBoundary *boundary = section->pBoundary; + + if (boundary) { + bool may_contain_road = false; + if (IsRegularScenerySection(section->SectionNumber)) { + if (IsScenerySectionDrivable(section->SectionNumber) || IsLODScenerySectionNumber(section->SectionNumber)) { + may_contain_road = true; + } + } + + if (may_contain_road && section->Status != TrackStreamingSection::ACTIVATED) { + const float small_test_time = kFuturePositionScale_TrackStreamer; + bVector2 test_pos = position_entry->Position + position_entry->Velocity * small_test_time; + float distance1 = boundary->GetDistanceOutside(&position_entry->Position, kMaxDistance_TrackStreamer); + float distance2 = boundary->GetDistanceOutside(&test_pos, kMaxDistance_TrackStreamer); + float approach_speed = (distance1 - distance2) * prediction_scale_a * prediction_scale_b; + float distance = distance1 - approach_speed; + if (distance < closest_distance) { + closest_distance = distance; + closest_section = section; + closest_position_entry = position_entry; + closest_approach_speed = approach_speed; + } + } + } + } + } + + need_loading_bar = closest_distance < kLoadingBarDistanceThreshold_TrackStreamer; + prev_need_loading_bar_26275 = need_loading_bar; + return need_loading_bar; +} + +void *TrackStreamer::AllocateUserMemory(int size, const char *debug_name, int offset) { +#ifndef MILESTONE_OPT + (void)debug_name; +#endif + + int allocation_params; + if (size > bLargestMalloc(7)) { + allocation_params = (offset & 0x1FFC) << 17 | 0x2000; + } else { + allocation_params = (offset & 0x1FFC) << 17 | 0x2047; + } +#ifdef MILESTONE_OPT + return bMalloc(size, debug_name, 0, allocation_params); +#else + return bMalloc(size, allocation_params); +#endif +} + +void TrackStreamer::FreeUserMemory(void *mem) { + int free_before = pMemoryPool->GetAmountFree(); + bFree(mem); + int size = pMemoryPool->GetAmountFree(); + (void)free_before; + (void)size; +} + +bool TrackStreamer::IsUserMemory(void *mem) { + int pos = static_cast(mem) - static_cast(pMemoryPoolMem); + return pMemoryPoolMem && pos >= 0 && pos < MemoryPoolSize; +} + +bool TrackStreamer::MakeSpaceInPool(int size, bool force_unloading) { + WaitForCurrentLoadingToComplete(); + while (bCountFreeMemory(7) < size) { + int amount_unloaded = UnloadLeastRecentlyUsedSection(); + if (amount_unloaded == 0 && (!force_unloading || !JettisonLeastImportantSection())) { + break; + } + } + + ForceHoleFillerMethod = 0; + DoHoleFilling(0x7FFFFFFF); + ForceHoleFillerMethod = -1; + return size <= bLargestMalloc(7); +} + +void TrackStreamer::MakeSpaceInPool(int size, void (*callback)(int), int param) { + if (LoadingPhase == LOADING_IDLE) { + IsLoadingInProgress(); + } + + if (!IsLoadingInProgress()) { + MakeSpaceInPool(size, true); + callback(param); + } else { + MakeSpaceInPoolSize = size; + MakeSpaceInPoolCallback = callback; + MakeSpaceInPoolCallbackParam = param; + pCallback = ReadyToMakeSpaceInPoolBridge; + CallbackParam = reinterpret_cast(this); + } +} + +void TrackStreamer::ReadyToMakeSpaceInPool() { + MakeSpaceInPool(MakeSpaceInPoolSize, true); + + void (*callback)(int) = MakeSpaceInPoolCallback; + int param = MakeSpaceInPoolCallbackParam; + MakeSpaceInPoolCallback = 0; + MakeSpaceInPoolCallbackParam = 0; + MakeSpaceInPoolSize = 0; + callback(param); +} + +void TrackStreamer::ReadyToMakeSpaceInPoolBridge(int param) { + reinterpret_cast(param)->ReadyToMakeSpaceInPool(); +} + +short TrackStreamer::GetPredictedZone(StreamingPositionEntry *position_entry) { + float speed = bLength(&position_entry->Velocity); + int predicted_zone = 0; + bool found_predicted_zone = false; + TrackPathZone *zone = 0; + bVector2 predict_position; + + while ((zone = TheTrackPathManager.FindZone(&position_entry->Position, TRACK_PATH_ZONE_STREAMER_PREDICTION, zone))) { + float elevation = zone->GetElevation(); + if ((0.0f < elevation && position_entry->Elevation < elevation) || (elevation < 0.0f && -elevation < position_entry->Elevation)) { + continue; + } + + float max_speed = kPredictedZoneStopProjectSpeed_TrackStreamer * kPredictedZoneScale_TrackStreamer; + float distance = speed * kPredictedZoneScale_TrackStreamer; + DrivableScenerySection *scenery_section; + if (max_speed < speed) { + predict_position = position_entry->Position; + } else if (kPredictedZoneMaxDistance_TrackStreamer < distance) { + bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneMaxDistance_TrackStreamer / speed); + } else { + bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneScale_TrackStreamer); + } + + scenery_section = TheVisibleSectionManager.FindDrivableSection(&predict_position); + if (scenery_section && zone->Data[0] != 0) { + short section_number = scenery_section->SectionNumber; + for (int i = 0; i <= 3; i++) { + if (zone->Data[i] == 0) { + break; + } + if (zone->Data[i] == section_number) { + found_predicted_zone = true; + predicted_zone = section_number; + break; + } + } + } + } + + if (found_predicted_zone) { + if (!bEqual(&predict_position, &position_entry->Position, kPredictedZoneEqualEpsilon_TrackStreamer)) { + for (int barrier_num = 0; barrier_num < NumBarriers; barrier_num++) { + TrackStreamingBarrier *barrier = &pBarriers[barrier_num]; + if (barrier->Intersects(&position_entry->Position, &predict_position)) { + found_predicted_zone = false; + predicted_zone = 0; + } + } + } + + if (found_predicted_zone) { + return predicted_zone; + } + } + + DrivableScenerySection *scenery_section = TheVisibleSectionManager.FindDrivableSection(&position_entry->Position); + if (scenery_section) { + predicted_zone = scenery_section->SectionNumber; + } + return predicted_zone; +} + +void TrackStreamer::ClearStreamingPositions() { + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->PositionSet = false; + position_entry->FollowingCar = false; + } +} + +void TrackStreamer::SetStreamingPosition(int position_number, const bVector3 *position) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->Position.x = position->x; + position_entry->Position.y = position->y; + position_entry->PredictedZone = 0; + position_entry->Elevation = position->z; + position_entry->Direction.y = 0.0f; + position_entry->PredictedZoneValidTime = -1; + position_entry->Velocity.x = 0.0f; + position_entry->Velocity.y = 0.0f; + position_entry->Direction.x = 0.0f; + position_entry->PositionSet = true; + position_entry->FollowingCar = false; + CurrentZoneNeedsRefreshing = true; +} + +void TrackStreamer::PredictStreamingPosition(int position_number, const bVector3 *position, const bVector3 *velocity, const bVector3 *direction, + bool following_car) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->Position.x = position->x; + position_entry->Position.y = position->y; + position_entry->Elevation = position->z; + position_entry->Velocity.x = velocity->x; + position_entry->Velocity.y = velocity->y; + position_entry->Direction.x = direction->x; + float direction_y = direction->y; + position_entry->FollowingCar = following_car; + position_entry->Direction.y = direction_y; + position_entry->PositionSet = true; +} + +bool TrackStreamer::DetermineCurrentZones(short *current_zones) { + bool changed = false; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + short current_zone = -1; + if (position_entry->PositionSet) { + current_zone = GetPredictedZone(position_entry); + } + + if (current_zone == position_entry->PredictedZone) { + position_entry->PredictedZoneValidTime += 1; + } else if (position_entry->PredictedZoneValidTime == -1) { + position_entry->PredictedZone = current_zone; + position_entry->PredictedZoneValidTime = 1000; + } else { + position_entry->PredictedZone = current_zone; + position_entry->PredictedZoneValidTime = 1; + } + + short section_number = position_entry->CurrentZone; + if (current_zone != section_number) { + if (position_entry->PredictedZoneValidTime < 0) { + current_zone = section_number; + } + if (current_zone != section_number) { + changed = true; + } + } + + current_zones[position_number] = current_zone; + } + + return changed || CurrentZoneNeedsRefreshing; +} + +void TrackStreamer::ServiceGameState() { + float start_time = GetDebugRealTime(); + HandleZoneSwitching(); + HandleSectionActivation(); + float time = GetDebugRealTime(); + (void)start_time; + (void)time; + + AmountNotRendered = 0; + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + if (!section->WasRendered && IsRegularScenerySection(section->SectionNumber)) { + AmountNotRendered += section->Size; + } + section->WasRendered = 0; + } +} + +void TrackStreamer::ServiceNonGameState() { + ProfileNode profile_node("TODO", 0); + float start_time = GetDebugRealTime(); + HandleLoading(); + float time = GetDebugRealTime(); + (void)start_time; + (void)time; +} + +void TrackStreamer::SetLoadingPhase(eLoadingPhase phase) { + LoadingPhase = phase; + if (phase == LOADING_IDLE || phase == LOADING_REGULAR_SECTIONS) { + SetQueuedFileMinPriority(0); + } else { + SetQueuedFileMinPriority(QueuedFileDefaultPriority); + } +} + +void TrackStreamer::BlockUntilLoadingComplete() { + RefreshLoading(); + WaitForCurrentLoadingToComplete(); +} + +void TrackStreamer::WaitForCurrentLoadingToComplete() { + while (!AreAllSectionsActivated()) { + HandleLoading(); + short section_to_activate = static_cast(GetSectionToActivate(0)); + if (section_to_activate != 0) { + ActivateSection(FindSection(section_to_activate)); + } + ServiceResourceLoading(); + bThreadYield(8); + } +} + +void TrackStreamer::RefreshLoading() { + CurrentZoneNeedsRefreshing = true; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->PredictedZoneValidTime = -1; + } + HandleZoneSwitching(); +} + +void TrackStreamer::HandleZoneSwitching() { + ProfileNode profile_node("TODO", 0); + short current_zones[2]; + bool current_zones_different; + if (!ZoneSwitchingDisabled && pMemoryPoolMem) { + current_zones_different = DetermineCurrentZones(current_zones); + if (current_zones_different) { + SwitchZones(current_zones); + } + } +} diff --git a/src/Speed/Indep/Src/World/TrackStreamer.hpp b/src/Speed/Indep/Src/World/TrackStreamer.hpp index 868d05048..c861d34b8 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.hpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.hpp @@ -24,6 +24,14 @@ struct DiscBundleSectionMember { }; struct DiscBundleSection { + int GetMemoryImageSize() { + return NumMembers * sizeof(DiscBundleSectionMember) + 0x14; + } + + DiscBundleSection *GetMemoryImageNext() { + return reinterpret_cast(reinterpret_cast(this) + GetMemoryImageSize()); + } + // total size: 0x114 int FileOffset; // offset 0x0, size 0x4 int FileSize; // offset 0x4, size 0x4 @@ -98,12 +106,59 @@ class TSMemoryNode : public bTNode { int Size; // offset 0xC, size 0x4 bool Allocated; // offset 0x10, size 0x1 char DebugName[32]; // offset 0x14, size 0x20 + + bool IsAllocated(); + + bool IsFree(); + + bool Contains(int address); + + int GetAddress(bool start_from_top, int size) { + if (start_from_top) { + return Address; + } + return Address + Size - size; + } }; // total size: 0x2754 class TSMemoryPool { public: + TSMemoryPool(int address, int size, const char *debug_name, int pool_num); + void *Malloc(int size, const char *debug_name, bool best_fit, bool allocate_from_top, int address); + void Free(void *memory); + int GetAmountFree(); + int GetLargestFreeBlock(); + TSMemoryNode *GetNextNode(bool start_from_top, TSMemoryNode *node = 0); + TSMemoryNode *GetFirstNode(bool start_from_top) { + return GetNextNode(start_from_top, 0); + } + TSMemoryNode *GetNextFreeNode(bool start_from_top, TSMemoryNode *node = 0); + TSMemoryNode *GetFirstFreeNode(bool start_from_top) { + return GetNextFreeNode(start_from_top, 0); + } + TSMemoryNode *GetNextAllocatedNode(bool start_from_top, TSMemoryNode *node = 0); + TSMemoryNode *GetFirstAllocatedNode(bool start_from_top); + bool IsUpdated() { + bool updated = Updated; + Updated = false; + return updated; + } + unsigned int GetPoolChecksum(); + void EnableTracing(bool enabled) { + TracingEnabled = enabled; + } + void DebugPrint(); + private: + static void *OverrideMalloc(void *pool, int size, const char *debug_text, int debug_line, int allocation_params); + static void OverrideFree(void *pool, void *ptr); + static int OverrideGetAmountFree(void *pool); + static int OverrideGetLargestFreeBlock(void *pool); + + TSMemoryNode *GetNewNode(int address, int size, bool allocated, const char *debug_name); + void RemoveNode(TSMemoryNode *node); + int PoolNum; // offset 0x0, size 0x4 const char *DebugName; // offset 0x4, size 0x4 int TotalSize; // offset 0x8, size 0x4 @@ -127,14 +182,23 @@ struct TrackStreamingInfo { struct TrackStreamingBarrier { // void EndianSwap() {} - // bool Intersects(const bVector2 *pointa, const bVector2 *pointb) {} + bool Intersects(const bVector2 *pointa, const bVector2 *pointb); bVector2 Points[2]; // offset 0x0, size 0x10 }; +struct HoleMovement { + // total size: 0x10 + int Address; // offset 0x0, size 0x4 + int NewAddress; // offset 0x4, size 0x4 + int Size; // offset 0x8, size 0x4 + unsigned int Checksum; // offset 0xC, size 0x4 +}; + // total size: 0x888 class TrackStreamer { public: + TrackStreamer(); enum eLoadingPhase { LOADING_IDLE = 0, ALLOCATING_TEXTURE_SECTIONS = 1, @@ -150,8 +214,6 @@ class TrackStreamer { int Unloader(bChunk *chunk); - void ClearCurrentZones(); - void InitMemoryPool(int size); void CloseMemoryPool(); @@ -168,8 +230,6 @@ class TrackStreamer { TrackStreamingSection *FindSectionByAddress(int address); - int GetCombinedSectionNumber(int section_number); - void InitRegion(const char *region_stream_filename, bool split_screen); void StartPermFileLoading(const char *filename); @@ -182,14 +242,6 @@ class TrackStreamer { void *AllocateMemory(TrackStreamingSection *section, int allocation_params); - void LoadDiscBundle(DiscBundleSection *disc_bundle); - - static void DiscBundleLoadedCallback(int param, int error_status); - - void DiscBundleLoadedCallback(DiscBundleSection *disc_bundle); - - void LoadSection(TrackStreamingSection *section); - void ActivateSection(TrackStreamingSection *section); void UnactivateSection(TrackStreamingSection *section); @@ -200,10 +252,6 @@ class TrackStreamer { bool NeedsGameStateActivation(TrackStreamingSection *section); - static void SectionLoadedCallback(int param, int error_status); - - void SectionLoadedCallback(TrackStreamingSection *section); - void EmptyCaffeineLayers(); static char *GetLoadingPhaseName(enum eLoadingPhase phase); @@ -222,7 +270,7 @@ class TrackStreamer { void UnJettisonSections(); - int BuildHoleMovements(struct HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, + int BuildHoleMovements(HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, int max_amount_to_move); int DoHoleFilling(int largest_free); @@ -262,8 +310,6 @@ class TrackStreamer { void ReadyToMakeSpaceInPool(); - bool DetermineCurrentZones(short *current_zones); - void ServiceGameState(); void PrintMemoryPool(); @@ -282,8 +328,6 @@ class TrackStreamer { void SetLoadingCallback(void (*callback)(int), int param); - void HandleZoneSwitching(); - void SwitchZones(short *current_zones); void HandleLoading(); @@ -310,6 +354,10 @@ class TrackStreamer { void ForceSectionToUnload(int section_number); + bool IsFarLoadingInProgress() { + return CurrentZoneFarLoad && IsLoadingInProgress(); + } + void DisableZoneSwitching() { ZoneSwitchingDisabled = true; } @@ -323,6 +371,18 @@ class TrackStreamer { } private: + void ClearCurrentZones(); + bool DetermineCurrentZones(short *current_zones); + void HandleZoneSwitching(); + int GetCombinedSectionNumber(int section_number); + static void DiscBundleLoadedCallback(int param, int error_status); + static void ReadyToMakeSpaceInPoolBridge(int param); + void DiscBundleLoadedCallback(DiscBundleSection *disc_bundle); + void LoadDiscBundle(DiscBundleSection *disc_bundle); + void LoadSection(TrackStreamingSection *section); + static void SectionLoadedCallback(int param, int error_status); + void SectionLoadedCallback(TrackStreamingSection *section); + TrackStreamingSection *pTrackStreamingSections; // offset 0x0, size 0x4 int NumTrackStreamingSections; // offset 0x4, size 0x4 DiscBundleSection *pDiscBundleSections; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/World/VisibleSection.cpp b/src/Speed/Indep/Src/World/VisibleSection.cpp index 935d9a975..8d8aef650 100644 --- a/src/Speed/Indep/Src/World/VisibleSection.cpp +++ b/src/Speed/Indep/Src/World/VisibleSection.cpp @@ -1,3 +1,925 @@ #include "VisibleSection.hpp" +#include "Scenery.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +int LoaderVisibleSections(bChunk *chunk) { + return TheVisibleSectionManager.Loader(chunk); +} + +int UnloaderVisibleSections(bChunk *chunk) { + return TheVisibleSectionManager.Unloader(chunk); +} + +void RefreshTrackStreamer(); +BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); +int Get2PlayerSectionNumber(int section_number, const char *build_platform); +int Get2PlayerSectionNumber(int section_number); +int Get1PlayerSectionNumber(int section_number_2p, const char *build_platform); +int GetBoundarySectionNumber(int section_number, const char *platform_name); +static bool MyIsPointInPoly(const bVector2 *point, const bVector2 *polygon, int num_points); +float bDistToLine(const bVector2 *point, const bVector2 *pointa, const bVector2 *pointb); + +struct SectionRemapper { + short SectionNumber; + short SectionNumber2P; +}; + +VisibleGroupInfo VisibleGroupInfoTable[5] = { + {"BARRIER_", 1}, + {"BARRIERS_", 1}, + {"PLAYER_BARRIERS_", 1}, + {"SCENERY_GROUP_", 1}, + {"FREE_ROAM", 0}, +}; +SectionRemapper SectionRemapperTable_Gamecube[129] = { + {GetScenerySectionNumber('D', 7), GetScenerySectionNumber('K', 1)}, + {GetScenerySectionNumber('D', 47), GetScenerySectionNumber('K', 41)}, + {GetScenerySectionNumber('D', 21), GetScenerySectionNumber('K', 2)}, + {GetScenerySectionNumber('D', 61), GetScenerySectionNumber('K', 42)}, + {GetScenerySectionNumber('D', 29), GetScenerySectionNumber('K', 3)}, + {GetScenerySectionNumber('D', 69), GetScenerySectionNumber('K', 43)}, + {GetScenerySectionNumber('D', 35), GetScenerySectionNumber('K', 4)}, + {GetScenerySectionNumber('D', 75), GetScenerySectionNumber('K', 44)}, + {GetScenerySectionNumber('D', 38), GetScenerySectionNumber('K', 5)}, + {GetScenerySectionNumber('D', 78), GetScenerySectionNumber('K', 45)}, + {GetScenerySectionNumber('F', 4), GetScenerySectionNumber('K', 6)}, + {GetScenerySectionNumber('F', 44), GetScenerySectionNumber('K', 46)}, + {GetScenerySectionNumber('F', 11), GetScenerySectionNumber('K', 7)}, + {GetScenerySectionNumber('F', 51), GetScenerySectionNumber('K', 47)}, + {GetScenerySectionNumber('F', 13), GetScenerySectionNumber('K', 8)}, + {GetScenerySectionNumber('F', 53), GetScenerySectionNumber('K', 48)}, + {GetScenerySectionNumber('G', 2), GetScenerySectionNumber('K', 9)}, + {GetScenerySectionNumber('G', 42), GetScenerySectionNumber('K', 49)}, + {GetScenerySectionNumber('G', 3), GetScenerySectionNumber('K', 10)}, + {GetScenerySectionNumber('G', 43), GetScenerySectionNumber('K', 50)}, + {GetScenerySectionNumber('G', 4), GetScenerySectionNumber('K', 11)}, + {GetScenerySectionNumber('G', 44), GetScenerySectionNumber('K', 51)}, + {GetScenerySectionNumber('G', 8), GetScenerySectionNumber('K', 12)}, + {GetScenerySectionNumber('G', 48), GetScenerySectionNumber('K', 52)}, + {GetScenerySectionNumber('G', 9), GetScenerySectionNumber('K', 13)}, + {GetScenerySectionNumber('G', 49), GetScenerySectionNumber('K', 53)}, + {GetScenerySectionNumber('G', 10), GetScenerySectionNumber('K', 14)}, + {GetScenerySectionNumber('G', 50), GetScenerySectionNumber('K', 54)}, + {GetScenerySectionNumber('G', 12), GetScenerySectionNumber('K', 15)}, + {GetScenerySectionNumber('G', 52), GetScenerySectionNumber('K', 55)}, + {GetScenerySectionNumber('G', 14), GetScenerySectionNumber('K', 16)}, + {GetScenerySectionNumber('G', 54), GetScenerySectionNumber('K', 56)}, + {GetScenerySectionNumber('G', 15), GetScenerySectionNumber('K', 17)}, + {GetScenerySectionNumber('G', 55), GetScenerySectionNumber('K', 57)}, + {GetScenerySectionNumber('G', 20), GetScenerySectionNumber('K', 18)}, + {GetScenerySectionNumber('G', 60), GetScenerySectionNumber('K', 58)}, + {GetScenerySectionNumber('G', 22), GetScenerySectionNumber('K', 19)}, + {GetScenerySectionNumber('G', 62), GetScenerySectionNumber('K', 59)}, + {GetScenerySectionNumber('G', 29), GetScenerySectionNumber('K', 20)}, + {GetScenerySectionNumber('G', 69), GetScenerySectionNumber('K', 60)}, + {GetScenerySectionNumber('G', 30), GetScenerySectionNumber('K', 21)}, + {GetScenerySectionNumber('G', 70), GetScenerySectionNumber('K', 61)}, + {GetScenerySectionNumber('H', 2), GetScenerySectionNumber('K', 22)}, + {GetScenerySectionNumber('H', 42), GetScenerySectionNumber('K', 62)}, + {GetScenerySectionNumber('H', 3), GetScenerySectionNumber('K', 23)}, + {GetScenerySectionNumber('H', 43), GetScenerySectionNumber('K', 63)}, + {GetScenerySectionNumber('H', 4), GetScenerySectionNumber('K', 24)}, + {GetScenerySectionNumber('H', 44), GetScenerySectionNumber('K', 64)}, + {GetScenerySectionNumber('H', 10), GetScenerySectionNumber('K', 25)}, + {GetScenerySectionNumber('H', 50), GetScenerySectionNumber('K', 65)}, + {GetScenerySectionNumber('H', 11), GetScenerySectionNumber('K', 26)}, + {GetScenerySectionNumber('H', 51), GetScenerySectionNumber('K', 66)}, + {GetScenerySectionNumber('H', 13), GetScenerySectionNumber('K', 27)}, + {GetScenerySectionNumber('H', 53), GetScenerySectionNumber('K', 67)}, + {GetScenerySectionNumber('H', 15), GetScenerySectionNumber('K', 28)}, + {GetScenerySectionNumber('H', 55), GetScenerySectionNumber('K', 68)}, + {GetScenerySectionNumber('H', 18), GetScenerySectionNumber('K', 29)}, + {GetScenerySectionNumber('H', 58), GetScenerySectionNumber('K', 69)}, + {GetScenerySectionNumber('I', 7), GetScenerySectionNumber('K', 30)}, + {GetScenerySectionNumber('I', 47), GetScenerySectionNumber('K', 70)}, + {GetScenerySectionNumber('I', 13), GetScenerySectionNumber('K', 31)}, + {GetScenerySectionNumber('I', 53), GetScenerySectionNumber('K', 71)}, + {GetScenerySectionNumber('I', 19), GetScenerySectionNumber('K', 32)}, + {GetScenerySectionNumber('I', 59), GetScenerySectionNumber('K', 72)}, + {GetScenerySectionNumber('I', 20), GetScenerySectionNumber('K', 33)}, + {GetScenerySectionNumber('I', 60), GetScenerySectionNumber('K', 73)}, + {GetScenerySectionNumber('I', 22), GetScenerySectionNumber('K', 34)}, + {GetScenerySectionNumber('I', 62), GetScenerySectionNumber('K', 74)}, + {GetScenerySectionNumber('I', 31), GetScenerySectionNumber('K', 35)}, + {GetScenerySectionNumber('I', 71), GetScenerySectionNumber('K', 75)}, + {GetScenerySectionNumber('I', 32), GetScenerySectionNumber('K', 36)}, + {GetScenerySectionNumber('I', 72), GetScenerySectionNumber('K', 76)}, + {GetScenerySectionNumber('J', 9), GetScenerySectionNumber('K', 37)}, + {GetScenerySectionNumber('J', 49), GetScenerySectionNumber('K', 77)}, + {GetScenerySectionNumber('J', 10), GetScenerySectionNumber('K', 38)}, + {GetScenerySectionNumber('J', 50), GetScenerySectionNumber('K', 78)}, + {GetScenerySectionNumber('O', 9), GetScenerySectionNumber('K', 39)}, + {GetScenerySectionNumber('O', 49), GetScenerySectionNumber('K', 79)}, + {GetScenerySectionNumber('P', 4), GetScenerySectionNumber('N', 1)}, + {GetScenerySectionNumber('P', 44), GetScenerySectionNumber('N', 41)}, + {GetScenerySectionNumber('P', 5), GetScenerySectionNumber('N', 2)}, + {GetScenerySectionNumber('P', 45), GetScenerySectionNumber('N', 42)}, + {GetScenerySectionNumber('P', 7), GetScenerySectionNumber('N', 3)}, + {GetScenerySectionNumber('P', 47), GetScenerySectionNumber('N', 43)}, + {GetScenerySectionNumber('P', 10), GetScenerySectionNumber('N', 4)}, + {GetScenerySectionNumber('P', 50), GetScenerySectionNumber('N', 44)}, + {GetScenerySectionNumber('P', 17), GetScenerySectionNumber('N', 5)}, + {GetScenerySectionNumber('P', 57), GetScenerySectionNumber('N', 45)}, + {GetScenerySectionNumber('P', 22), GetScenerySectionNumber('N', 6)}, + {GetScenerySectionNumber('P', 62), GetScenerySectionNumber('N', 46)}, + {GetScenerySectionNumber('P', 33), GetScenerySectionNumber('N', 7)}, + {GetScenerySectionNumber('P', 73), GetScenerySectionNumber('N', 47)}, + {GetScenerySectionNumber('Q', 3), GetScenerySectionNumber('N', 8)}, + {GetScenerySectionNumber('Q', 43), GetScenerySectionNumber('N', 48)}, + {GetScenerySectionNumber('Q', 5), GetScenerySectionNumber('N', 9)}, + {GetScenerySectionNumber('Q', 45), GetScenerySectionNumber('N', 49)}, + {GetScenerySectionNumber('Q', 10), GetScenerySectionNumber('N', 10)}, + {GetScenerySectionNumber('Q', 50), GetScenerySectionNumber('N', 50)}, + {GetScenerySectionNumber('Q', 12), GetScenerySectionNumber('N', 11)}, + {GetScenerySectionNumber('Q', 52), GetScenerySectionNumber('N', 51)}, + {GetScenerySectionNumber('Q', 16), GetScenerySectionNumber('N', 12)}, + {GetScenerySectionNumber('Q', 56), GetScenerySectionNumber('N', 52)}, + {GetScenerySectionNumber('Q', 23), GetScenerySectionNumber('N', 13)}, + {GetScenerySectionNumber('Q', 63), GetScenerySectionNumber('N', 53)}, + {GetScenerySectionNumber('R', 1), GetScenerySectionNumber('N', 14)}, + {GetScenerySectionNumber('R', 41), GetScenerySectionNumber('N', 54)}, + {GetScenerySectionNumber('R', 3), GetScenerySectionNumber('N', 15)}, + {GetScenerySectionNumber('R', 43), GetScenerySectionNumber('N', 55)}, + {GetScenerySectionNumber('R', 4), GetScenerySectionNumber('N', 16)}, + {GetScenerySectionNumber('R', 44), GetScenerySectionNumber('N', 56)}, + {GetScenerySectionNumber('R', 7), GetScenerySectionNumber('N', 17)}, + {GetScenerySectionNumber('R', 47), GetScenerySectionNumber('N', 57)}, + {GetScenerySectionNumber('R', 16), GetScenerySectionNumber('N', 18)}, + {GetScenerySectionNumber('R', 56), GetScenerySectionNumber('N', 58)}, + {GetScenerySectionNumber('R', 18), GetScenerySectionNumber('N', 19)}, + {GetScenerySectionNumber('R', 58), GetScenerySectionNumber('N', 59)}, + {GetScenerySectionNumber('R', 26), GetScenerySectionNumber('N', 20)}, + {GetScenerySectionNumber('R', 66), GetScenerySectionNumber('N', 60)}, + {GetScenerySectionNumber('R', 33), GetScenerySectionNumber('N', 21)}, + {GetScenerySectionNumber('R', 73), GetScenerySectionNumber('N', 61)}, + {GetScenerySectionNumber('V', 1), GetScenerySectionNumber('V', 99)}, + {GetScenerySectionNumber('V', 4), GetScenerySectionNumber('V', 98)}, + {GetScenerySectionNumber('V', 6), GetScenerySectionNumber('V', 97)}, + {GetScenerySectionNumber('V', 14), GetScenerySectionNumber('V', 96)}, + {GetScenerySectionNumber('V', 19), GetScenerySectionNumber('V', 95)}, + {GetScenerySectionNumber('V', 22), GetScenerySectionNumber('V', 94)}, + {GetScenerySectionNumber('V', 59), GetScenerySectionNumber('V', 93)}, + {GetScenerySectionNumber('V', 71), GetScenerySectionNumber('V', 92)}, + {GetScenerySectionNumber('V', 70), GetScenerySectionNumber('V', 91)}, +}; +SectionRemapper SectionRemapperTable[134] = { + {GetScenerySectionNumber('Q', 3), GetScenerySectionNumber('Q', 39)}, + {GetScenerySectionNumber('Q', 43), GetScenerySectionNumber('Q', 79)}, + {GetScenerySectionNumber('G', 8), GetScenerySectionNumber('G', 39)}, + {GetScenerySectionNumber('G', 48), GetScenerySectionNumber('G', 79)}, + {GetScenerySectionNumber('F', 11), GetScenerySectionNumber('F', 39)}, + {GetScenerySectionNumber('F', 51), GetScenerySectionNumber('F', 79)}, + {GetScenerySectionNumber('H', 10), GetScenerySectionNumber('H', 39)}, + {GetScenerySectionNumber('H', 50), GetScenerySectionNumber('H', 79)}, + {GetScenerySectionNumber('Q', 12), GetScenerySectionNumber('Q', 38)}, + {GetScenerySectionNumber('Q', 52), GetScenerySectionNumber('Q', 78)}, + {GetScenerySectionNumber('G', 12), GetScenerySectionNumber('G', 38)}, + {GetScenerySectionNumber('G', 52), GetScenerySectionNumber('G', 78)}, + {GetScenerySectionNumber('H', 4), GetScenerySectionNumber('H', 38)}, + {GetScenerySectionNumber('H', 44), GetScenerySectionNumber('H', 78)}, + {GetScenerySectionNumber('G', 10), GetScenerySectionNumber('G', 37)}, + {GetScenerySectionNumber('G', 50), GetScenerySectionNumber('G', 77)}, + {GetScenerySectionNumber('G', 20), GetScenerySectionNumber('G', 36)}, + {GetScenerySectionNumber('G', 60), GetScenerySectionNumber('G', 76)}, + {GetScenerySectionNumber('P', 17), GetScenerySectionNumber('Q', 37)}, + {GetScenerySectionNumber('P', 57), GetScenerySectionNumber('Q', 77)}, + {GetScenerySectionNumber('P', 4), GetScenerySectionNumber('Q', 33)}, + {GetScenerySectionNumber('P', 44), GetScenerySectionNumber('Q', 73)}, + {GetScenerySectionNumber('P', 5), GetScenerySectionNumber('Q', 34)}, + {GetScenerySectionNumber('P', 45), GetScenerySectionNumber('Q', 74)}, + {GetScenerySectionNumber('P', 7), GetScenerySectionNumber('Q', 35)}, + {GetScenerySectionNumber('P', 47), GetScenerySectionNumber('Q', 75)}, + {GetScenerySectionNumber('P', 10), GetScenerySectionNumber('Q', 36)}, + {GetScenerySectionNumber('P', 50), GetScenerySectionNumber('Q', 76)}, + {GetScenerySectionNumber('D', 35), GetScenerySectionNumber('D', 39)}, + {GetScenerySectionNumber('D', 75), GetScenerySectionNumber('D', 79)}, + {GetScenerySectionNumber('D', 16), GetScenerySectionNumber('C', 38)}, + {GetScenerySectionNumber('D', 56), GetScenerySectionNumber('C', 78)}, + {GetScenerySectionNumber('D', 21), GetScenerySectionNumber('C', 37)}, + {GetScenerySectionNumber('D', 61), GetScenerySectionNumber('C', 77)}, + {GetScenerySectionNumber('T', 6), GetScenerySectionNumber('T', 34)}, + {GetScenerySectionNumber('T', 46), GetScenerySectionNumber('T', 74)}, + {GetScenerySectionNumber('T', 8), GetScenerySectionNumber('T', 35)}, + {GetScenerySectionNumber('T', 48), GetScenerySectionNumber('T', 75)}, + {GetScenerySectionNumber('T', 9), GetScenerySectionNumber('T', 36)}, + {GetScenerySectionNumber('T', 49), GetScenerySectionNumber('T', 76)}, + {GetScenerySectionNumber('T', 13), GetScenerySectionNumber('T', 37)}, + {GetScenerySectionNumber('T', 53), GetScenerySectionNumber('T', 77)}, + {GetScenerySectionNumber('T', 17), GetScenerySectionNumber('T', 38)}, + {GetScenerySectionNumber('T', 57), GetScenerySectionNumber('T', 78)}, + {GetScenerySectionNumber('T', 22), GetScenerySectionNumber('T', 39)}, + {GetScenerySectionNumber('T', 62), GetScenerySectionNumber('T', 79)}, + {GetScenerySectionNumber('R', 16), GetScenerySectionNumber('S', 36)}, + {GetScenerySectionNumber('R', 56), GetScenerySectionNumber('S', 76)}, + {GetScenerySectionNumber('R', 26), GetScenerySectionNumber('S', 38)}, + {GetScenerySectionNumber('R', 66), GetScenerySectionNumber('S', 78)}, + {GetScenerySectionNumber('R', 17), GetScenerySectionNumber('S', 37)}, + {GetScenerySectionNumber('R', 57), GetScenerySectionNumber('S', 77)}, + {GetScenerySectionNumber('R', 18), GetScenerySectionNumber('S', 39)}, + {GetScenerySectionNumber('R', 58), GetScenerySectionNumber('S', 79)}, + {GetScenerySectionNumber('R', 3), GetScenerySectionNumber('S', 35)}, + {GetScenerySectionNumber('R', 43), GetScenerySectionNumber('S', 75)}, + {GetScenerySectionNumber('R', 7), GetScenerySectionNumber('S', 34)}, + {GetScenerySectionNumber('R', 47), GetScenerySectionNumber('S', 74)}, + {GetScenerySectionNumber('R', 33), GetScenerySectionNumber('S', 33)}, + {GetScenerySectionNumber('R', 73), GetScenerySectionNumber('S', 73)}, + {GetScenerySectionNumber('R', 1), GetScenerySectionNumber('S', 28)}, + {GetScenerySectionNumber('R', 41), GetScenerySectionNumber('S', 68)}, + {GetScenerySectionNumber('R', 2), GetScenerySectionNumber('S', 27)}, + {GetScenerySectionNumber('R', 42), GetScenerySectionNumber('S', 67)}, + {GetScenerySectionNumber('R', 14), GetScenerySectionNumber('S', 26)}, + {GetScenerySectionNumber('R', 54), GetScenerySectionNumber('S', 66)}, + {GetScenerySectionNumber('R', 20), GetScenerySectionNumber('S', 25)}, + {GetScenerySectionNumber('R', 60), GetScenerySectionNumber('S', 65)}, + {GetScenerySectionNumber('R', 21), GetScenerySectionNumber('S', 24)}, + {GetScenerySectionNumber('R', 61), GetScenerySectionNumber('S', 64)}, + {GetScenerySectionNumber('R', 27), GetScenerySectionNumber('S', 23)}, + {GetScenerySectionNumber('R', 67), GetScenerySectionNumber('S', 63)}, + {GetScenerySectionNumber('R', 28), GetScenerySectionNumber('L', 36)}, + {GetScenerySectionNumber('R', 68), GetScenerySectionNumber('L', 76)}, + {GetScenerySectionNumber('O', 35), GetScenerySectionNumber('S', 32)}, + {GetScenerySectionNumber('O', 75), GetScenerySectionNumber('S', 72)}, + {GetScenerySectionNumber('O', 9), GetScenerySectionNumber('S', 31)}, + {GetScenerySectionNumber('O', 49), GetScenerySectionNumber('S', 71)}, + {GetScenerySectionNumber('O', 26), GetScenerySectionNumber('S', 30)}, + {GetScenerySectionNumber('O', 66), GetScenerySectionNumber('S', 70)}, + {GetScenerySectionNumber('O', 32), GetScenerySectionNumber('S', 29)}, + {GetScenerySectionNumber('O', 72), GetScenerySectionNumber('S', 69)}, + {GetScenerySectionNumber('O', 35), GetScenerySectionNumber('L', 37)}, + {GetScenerySectionNumber('O', 75), GetScenerySectionNumber('L', 77)}, + {GetScenerySectionNumber('I', 2), GetScenerySectionNumber('I', 39)}, + {GetScenerySectionNumber('I', 42), GetScenerySectionNumber('I', 79)}, + {GetScenerySectionNumber('I', 3), GetScenerySectionNumber('I', 38)}, + {GetScenerySectionNumber('I', 43), GetScenerySectionNumber('I', 78)}, + {GetScenerySectionNumber('I', 1), GetScenerySectionNumber('I', 37)}, + {GetScenerySectionNumber('I', 41), GetScenerySectionNumber('I', 77)}, + {GetScenerySectionNumber('I', 20), GetScenerySectionNumber('I', 36)}, + {GetScenerySectionNumber('I', 60), GetScenerySectionNumber('I', 76)}, + {GetScenerySectionNumber('I', 22), GetScenerySectionNumber('I', 35)}, + {GetScenerySectionNumber('I', 62), GetScenerySectionNumber('I', 75)}, + {GetScenerySectionNumber('I', 31), GetScenerySectionNumber('L', 38)}, + {GetScenerySectionNumber('I', 71), GetScenerySectionNumber('L', 78)}, + {GetScenerySectionNumber('I', 32), GetScenerySectionNumber('L', 39)}, + {GetScenerySectionNumber('I', 72), GetScenerySectionNumber('L', 79)}, + {GetScenerySectionNumber('J', 9), GetScenerySectionNumber('J', 39)}, + {GetScenerySectionNumber('J', 49), GetScenerySectionNumber('J', 79)}, + {GetScenerySectionNumber('J', 10), GetScenerySectionNumber('J', 38)}, + {GetScenerySectionNumber('J', 50), GetScenerySectionNumber('J', 78)}, + {GetScenerySectionNumber('F', 6), GetScenerySectionNumber('F', 38)}, + {GetScenerySectionNumber('F', 46), GetScenerySectionNumber('F', 78)}, + {GetScenerySectionNumber('F', 13), GetScenerySectionNumber('F', 37)}, + {GetScenerySectionNumber('F', 53), GetScenerySectionNumber('F', 77)}, + {GetScenerySectionNumber('H', 2), GetScenerySectionNumber('H', 37)}, + {GetScenerySectionNumber('H', 42), GetScenerySectionNumber('H', 77)}, + {GetScenerySectionNumber('H', 3), GetScenerySectionNumber('H', 36)}, + {GetScenerySectionNumber('H', 43), GetScenerySectionNumber('H', 76)}, + {GetScenerySectionNumber('H', 11), GetScenerySectionNumber('H', 35)}, + {GetScenerySectionNumber('H', 51), GetScenerySectionNumber('H', 75)}, + {GetScenerySectionNumber('H', 15), GetScenerySectionNumber('H', 34)}, + {GetScenerySectionNumber('H', 55), GetScenerySectionNumber('H', 74)}, + {GetScenerySectionNumber('G', 17), GetScenerySectionNumber('G', 35)}, + {GetScenerySectionNumber('G', 57), GetScenerySectionNumber('G', 75)}, + {GetScenerySectionNumber('V', 14), GetScenerySectionNumber('V', 99)}, + {GetScenerySectionNumber('V', 4), GetScenerySectionNumber('V', 98)}, + {GetScenerySectionNumber('V', 41), GetScenerySectionNumber('V', 97)}, + {GetScenerySectionNumber('V', 31), GetScenerySectionNumber('V', 96)}, + {GetScenerySectionNumber('V', 55), GetScenerySectionNumber('V', 95)}, + {GetScenerySectionNumber('E', 2), GetScenerySectionNumber('E', 39)}, + {GetScenerySectionNumber('E', 42), GetScenerySectionNumber('E', 79)}, + {GetScenerySectionNumber('E', 3), GetScenerySectionNumber('E', 38)}, + {GetScenerySectionNumber('E', 43), GetScenerySectionNumber('E', 78)}, + {GetScenerySectionNumber('E', 5), GetScenerySectionNumber('E', 37)}, + {GetScenerySectionNumber('E', 45), GetScenerySectionNumber('E', 77)}, + {GetScenerySectionNumber('E', 8), GetScenerySectionNumber('E', 36)}, + {GetScenerySectionNumber('E', 48), GetScenerySectionNumber('E', 76)}, + {GetScenerySectionNumber('E', 1), GetScenerySectionNumber('E', 35)}, + {GetScenerySectionNumber('E', 41), GetScenerySectionNumber('E', 75)}, + {GetScenerySectionNumber('M', 12), GetScenerySectionNumber('M', 39)}, + {GetScenerySectionNumber('M', 52), GetScenerySectionNumber('M', 79)}, + {GetScenerySectionNumber('C', 9), GetScenerySectionNumber('C', 39)}, +}; + VisibleSectionManager TheVisibleSectionManager; // size: 0x6830 +int ScenerySectionLODOffset = 0; +DrivableScenerySection *pSectionD9 = 0; +DrivableScenerySection *pSectionC14 = 0; +char *bGetPlatformName(); +int bStrNICmp(const char *s1, const char *s2, int n); + +static bool initialized_VisibleSection = false; +static int map_table_VisibleSection[2800]; +static int counter_VisibleSection = 0; +static char text_VisibleSection[4][16]; + +VisibleSectionManager::VisibleSectionManager() { + pBoundaryChunks = 0; + pInfo = 0; + pActiveOverlay = 0; + pUndoOverlay = 0; + + bMemSet(UserInfoTable, 0, sizeof(UserInfoTable)); + NumAllocatedUserInfo = 0; + + bNode *head = &UnallocatedUserInfoList.HeadNode; + for (int i = 0; i < 512; i++) { + VisibleSectionUserInfo *user_info = &UserInfoStorageTable[i]; + bNode *tail = head->Prev; + + tail->Next = reinterpret_cast(user_info); + head->Prev = reinterpret_cast(user_info); + reinterpret_cast(user_info)->Prev = tail; + reinterpret_cast(user_info)->Next = head; + } + + VisibleBitTables = 0; + bMemSet(EnabledGroups, 0, sizeof(EnabledGroups)); +} + +char *GetScenerySectionName(char *name, int section_number) { + if (section_number < 1) { + name[0] = '-'; + name[1] = '-'; + name[2] = '\0'; + } else { + bSPrintf(name, "%c%d", GetScenerySectionLetter(section_number), section_number % 100); + } + + return name; +} + +char *GetScenerySectionName(int section_number) { + unsigned int index = static_cast(counter_VisibleSection) & 3; + counter_VisibleSection += 1; + return GetScenerySectionName(text_VisibleSection[index], section_number); +} + +static inline bool PointInBBox(const bVector2 *point, const bVector2 *bbox_min, const bVector2 *bbox_max) { + return point->x >= bbox_min->x && point->x <= bbox_max->x && point->y >= bbox_min->y && point->y <= bbox_max->y; +} + +bool VisibleSectionBoundary::IsPointInside(const bVector2 *point) { + if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, 0.0f)) { + return false; + } + + return MyIsPointInPoly(point, Points, NumPoints); +} + +float VisibleSectionBoundary::GetDistanceOutside(const bVector2 *point, float max_distance) { + if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, max_distance)) { + return max_distance; + } + + if (IsPointInside(point)) { + return 0.0f; + } + + float closest_distance = max_distance; + { + int point_number = 0; + while (point_number < NumPoints) { + int next = point_number + 1; + bVector2 *point1 = GetPoint(point_number); + bVector2 *point2 = GetPoint(next - (next / NumPoints) * NumPoints); + float distance = bDistToLine(point, point1, point2); + if (distance < closest_distance) { + closest_distance = distance; + } + point_number = next; + } + } + + return closest_distance; +} + +void DrivableScenerySection::AddVisibleSection(int section_number) { + if (NumVisibleSections < MaxVisibleSections && !IsSectionVisible(section_number)) { + short num_visible_sections = NumVisibleSections; + NumVisibleSections = num_visible_sections + 1; + VisibleSections[num_visible_sections] = static_cast(section_number); + if (MostVisibleSections < NumVisibleSections) { + MostVisibleSections = NumVisibleSections; + } + } +} + +int DrivableScenerySection::IsSectionVisible(int section_number) { + for (int i = 0; i < NumVisibleSections; i++) { + if (VisibleSections[i] == section_number) { + return 1; + } + } + return 0; +} + +void DrivableScenerySection::RemoveVisibleSection(int section_number) { + { + int n = 0; + if (n >= NumVisibleSections) { + return; + } + + do { + if (VisibleSections[n] == section_number) { + { + int i = n; + + if (i < NumVisibleSections - 1) { + do { + VisibleSections[i] = VisibleSections[i + 1]; + i++; + } while (i < NumVisibleSections - 1); + } + } + + NumVisibleSections--; + VisibleSections[NumVisibleSections] = 0; + return; + } + + n++; + } while (n < NumVisibleSections); + } +} + +void DrivableScenerySection::SortVisibleSections() { + bool swap; + do { + swap = false; + if (NumVisibleSections - 1 > 0) { + for (int i = 0; i < NumVisibleSections - 1; i++) { + short a = VisibleSections[i]; + short b = VisibleSections[i + 1]; + if (b < a) { + VisibleSections[i + 1] = a; + swap = true; + VisibleSections[i] = b; + } + } + } + } while (swap); +} + +VisibleSectionUserInfo *VisibleSectionManager::AllocateUserInfo(int section_number) { + VisibleSectionUserInfo *user_info = UserInfoTable[section_number]; + if (!user_info) { + user_info = reinterpret_cast(UnallocatedUserInfoList.HeadNode.Next); + NumAllocatedUserInfo += 1; + + bNode *next = reinterpret_cast(user_info)->Next; + bNode *prev = reinterpret_cast(user_info)->Prev; + prev->Next = next; + next->Prev = prev; + + bMemSet(user_info, 0, sizeof(VisibleSectionUserInfo)); + UserInfoTable[section_number] = user_info; + } + + user_info->ReferenceCount += 1; + return user_info; +} + +void VisibleSectionManager::UnallocateUserInfo(int section_number) { + VisibleSectionUserInfo *user_info = UserInfoTable[section_number]; + if (!user_info) { + return; + } + + int ref_count = user_info->ReferenceCount - 1; + user_info->ReferenceCount = ref_count; + if (ref_count != 0) { + return; + } + + bNode *tail = UnallocatedUserInfoList.HeadNode.Prev; + NumAllocatedUserInfo -= 1; + tail->Next = reinterpret_cast(user_info); + UnallocatedUserInfoList.HeadNode.Prev = reinterpret_cast(user_info); + reinterpret_cast(user_info)->Prev = tail; + reinterpret_cast(user_info)->Next = &UnallocatedUserInfoList.HeadNode; + UserInfoTable[section_number] = 0; +} + +VisibleSectionBoundary *VisibleSectionManager::FindBoundary(int section_number) { + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->SectionNumber == section_number) { + return boundary; + } + } + + for (VisibleSectionBoundary *boundary = NonDrivableBoundaryList.GetHead(); boundary != NonDrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->SectionNumber == section_number) { + return boundary; + } + } + + return 0; +} + +VisibleSectionBoundary *VisibleSectionManager::FindClosestBoundary(const bVector2 *point, float *distance) { + float closest_distance = 9999999.0f; + VisibleSectionBoundary *closest_boundary = 0; + + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->IsPointInside(point)) { + closest_distance = 0.0f; + DrivableBoundaryList.Remove(boundary); + DrivableBoundaryList.AddHead(boundary); + closest_boundary = boundary; + break; + } + + float boundary_distance = boundary->GetDistanceOutside(point, closest_distance); + if (!closest_boundary || boundary_distance < closest_distance || + (boundary_distance == closest_distance && boundary->SectionNumber < closest_boundary->SectionNumber)) { + closest_distance = boundary_distance; + closest_boundary = boundary; + } + } + + if (distance) { + *distance = closest_distance; + } + + return closest_boundary; +} + +VisibleSectionBoundary *VisibleSectionManager::FindBoundary(const bVector2 *point) { + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->IsPointInside(point)) { + DrivableBoundaryList.Remove(boundary); + DrivableBoundaryList.AddHead(boundary); + return boundary; + } + } + + float distance_to_boundary; + VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance_to_boundary); + if (distance_to_boundary >= 0.1f) { + return 0; + } + + return boundary; +} + +DrivableScenerySection *VisibleSectionManager::FindDrivableSection(const bVector2 *point) { + for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { + if (section->pBoundary->IsPointInside(point)) { + DrivableSectionList.Remove(section); + DrivableSectionList.AddHead(section); + return section; + } + } + + float distance; + VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance); + if (distance < 0.1f) { + return FindDrivableSection(boundary->SectionNumber); + } + + return 0; +} + +DrivableScenerySection *VisibleSectionManager::FindDrivableSection(int section_number) { + for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { + if (section->SectionNumber == section_number) { + return section; + } + } + + return 0; +} + +LoadingSection *VisibleSectionManager::FindLoadingSection(int section_number) { + for (LoadingSection *loading_section = LoadingSectionList.GetHead(); loading_section != LoadingSectionList.EndOfList(); + loading_section = loading_section->GetNext()) { + short target_section_number = static_cast(section_number); + short *drivable_sections = loading_section->DrivableSections; + int num_drivable_sections = loading_section->NumDrivableSections; + + for (int i = 0; i < num_drivable_sections; i++) { + if (drivable_sections[i] == target_section_number) { + return loading_section; + } + } + } + + return 0; +} + +int VisibleSectionManager::GetSectionsToLoad(LoadingSection *loading_section, short *section_numbers, int max_sections) { + if (!loading_section) { + return 0; + } + + int num_sections = 0; + for (int n = 0; n < loading_section->NumDrivableSections; n++) { + DrivableScenerySection *drivable_section = FindDrivableSection(loading_section->DrivableSections[n]); + if (!drivable_section) { + continue; + } + + for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { + int section_number = drivable_section->GetVisibleSection(i); + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + } + } + + for (int i = 0; i < loading_section->NumExtraSections; i++) { + int section_number = loading_section->ExtraSections[i]; + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + + if (IsScenerySectionDrivable(section_number)) { + section_number = GetLODScenerySectionNumber(section_number); + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + } + } + + return num_sections; +} + +VisibleGroupInfo *VisibleSectionManager::GetGroupInfo(const char *selection_set_name) { + VisibleGroupInfo *group_info = VisibleGroupInfoTable; + for (int i = 0; i < 5; i++) { + int name_length = bStrLen(group_info->SelectionSetName); + if (bStrNICmp(selection_set_name, group_info->SelectionSetName, name_length) == 0) { + return group_info; + } + group_info += 1; + } + return 0; +} + +void VisibleSectionManager::ActivateOverlay(const char *name) { + VisibleSectionOverlay *overlay = OverlayList.GetHead(); + while (overlay != OverlayList.EndOfList()) { + if (bStrICmp(overlay->Name, name) == 0) { + break; + } + overlay = overlay->GetNext(); + } + + if (overlay == OverlayList.EndOfList() || overlay == pActiveOverlay) { + return; + } + + if (pActiveOverlay) { + UnactivateOverlay(); + } + + DisablePrecullerCounter += 1; + pActiveOverlay = overlay; + VisibleSectionOverlay *undo_overlay = new VisibleSectionOverlay; + undo_overlay->NumEntries = 0; + bMemSet(undo_overlay->Name, 0, sizeof(undo_overlay->Name)); + bSafeStrCpy(undo_overlay->Name, "Undo", sizeof(undo_overlay->Name)); + pUndoOverlay = undo_overlay; + ActivateOverlay(overlay, undo_overlay); + RefreshTrackStreamer(); +} + +void VisibleSectionManager::ActivateOverlay(VisibleSectionOverlay *overlay, VisibleSectionOverlay *undo_overlay) { + for (int i = 0; i < overlay->NumEntries; i++) { + OverlayEntry *entry = &overlay->EntryTable[i]; + DrivableScenerySection *section = FindDrivableSection(entry->DrivableSectionNumber); + if (!section) { + continue; + } + + bool changed = false; + if (entry->AddRemove == 0) { + if (section->IsSectionVisible(entry->SectionNumber)) { + changed = true; + section->RemoveVisibleSection(entry->SectionNumber); + } + } else if (!section->IsSectionVisible(entry->SectionNumber)) { + changed = true; + section->AddVisibleSection(entry->SectionNumber); + } + + if (changed) { + section->SortVisibleSections(); + if (undo_overlay) { + OverlayEntry *undo_entry = &undo_overlay->EntryTable[undo_overlay->NumEntries]; + undo_overlay->NumEntries += 1; + *undo_entry = *entry; + undo_entry->AddRemove = entry->AddRemove == 0; + } + } + } +} + +void VisibleSectionManager::UnactivateOverlay() { + if (pActiveOverlay) { + DisablePrecullerCounter -= 1; + ActivateOverlay(pUndoOverlay, 0); + if (pUndoOverlay) { + delete pUndoOverlay; + } + pActiveOverlay = 0; + } +} + +void VisibleSectionManager::EnableGroup(unsigned int group_name) { + for (int i = 0; i < 0x100; i++) { + if (EnabledGroups[i] == 0) { + EnabledGroups[i] = group_name; + return; + } + } +} + +int VisibleSectionManager::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x80034150) { + pInfo = 0; + DrivableBoundaryList.InitList(); + NonDrivableBoundaryList.InitList(); + LoadingSectionList.InitList(); + DrivableSectionList.InitList(); + return 1; + } + + if (chunk->GetID() == 0x34158) { + reinterpret_cast(chunk->GetData())->Remove(); + return 1; + } + + return 0; +} + +int Get2PlayerSectionNumber(int section_number, const char *build_platform) { + if (bStrICmp(build_platform, "PC") != 0) { + char section_letter = GetScenerySectionLetter(section_number); + if (section_letter == 'Y') { + return static_cast(section_number % 100 + 0x8FC); + } + + if (section_letter == 'X') { + return static_cast(section_number % 100 + 0x834); + } + + SectionRemapper *remap_table = SectionRemapperTable; + int table_size = 134; + if (bStrICmp(build_platform, "GAMECUBE") == 0) { + table_size = 129; + remap_table = SectionRemapperTable_Gamecube; + } + + for (int n = 0; n < table_size; n++) { + if (remap_table[n].SectionNumber == section_number) { + return remap_table[n].SectionNumber2P; + } + } + } + + return section_number; +} + +int Get2PlayerSectionNumber(int section_number) { + return Get2PlayerSectionNumber(section_number, bGetPlatformName()); +} + +int Get1PlayerSectionNumber(int section_number_2p, const char *build_platform) { + if (!initialized_VisibleSection) { + initialized_VisibleSection = true; + bMemSet(map_table_VisibleSection, 0, sizeof(map_table_VisibleSection)); + for (int sec_1p = 0; sec_1p < 2800; sec_1p++) { + int sec_2p = Get2PlayerSectionNumber(sec_1p, build_platform); + if (sec_2p != sec_1p) { + map_table_VisibleSection[sec_2p] = sec_1p; + } + } + } + + if (map_table_VisibleSection[section_number_2p] != 0) { + return map_table_VisibleSection[section_number_2p]; + } + return section_number_2p; +} + +int GetBoundarySectionNumber(int section_number, const char *platform_name) { + int boundary_section_number = Get1PlayerSectionNumber(section_number, platform_name); + int subsection_number = boundary_section_number % 100; + int is_boundary_section = 0; + + if (subsection_number >= ScenerySectionLODOffset) { + is_boundary_section = subsection_number < ScenerySectionLODOffset * 2; + } + + if (is_boundary_section) { + boundary_section_number -= ScenerySectionLODOffset; + } + + return boundary_section_number; +} + +static bool MyIsPointInPoly(const bVector2 *point, const bVector2 *points, int num_points) { + float x = point->x; + float y = point->y; + bool inside = false; + int j = num_points - 1; + + for (int i = 0; i < num_points; i++) { + float point_y = points[i].y; + if (((point_y <= y && y < points[j].y) || (points[j].y <= y && y < point_y)) && + x < ((points[j].x - points[i].x) * (y - point_y)) / (points[j].y - point_y) + points[i].x) { + inside = !inside; + } + + j = i; + } + + return inside; +} + +int VisibleSectionManager::Loader(bChunk *chunk) { + if (chunk->GetID() == 0x80034150) { + bChunk *first_chunk = chunk->GetFirstChunk(); + bChunk *current_chunk = first_chunk; + bChunk *last_chunk = chunk->GetLastChunk(); + + while (current_chunk < last_chunk) { + unsigned int current_chunk_id = current_chunk->GetID(); + if (current_chunk_id == 0x34152) { + VisibleSectionBoundary *boundary = reinterpret_cast(current_chunk->GetData()); + VisibleSectionBoundary *last_boundary = reinterpret_cast(current_chunk->GetLastChunk()); + + if (boundary < last_boundary) { + do { + boundary->EndianSwap(); + if (IsScenerySectionDrivable(boundary->GetSectionNumber())) { + DrivableBoundaryList.AddTail(boundary); + } else { + NonDrivableBoundaryList.AddTail(boundary); + } + + boundary = reinterpret_cast( + reinterpret_cast(boundary) + boundary->GetMemoryImageSize()); + } while (boundary < last_boundary); + } + } else if (current_chunk_id == 0x34153) { + DrivableScenerySection *section = reinterpret_cast(current_chunk->GetData()); + DrivableScenerySection *last_section = reinterpret_cast(current_chunk->GetLastChunk()); + + if (section < last_section) { + do { + DrivableSectionList.AddTail(section); + section->EndianSwap(); + section->pBoundary = FindBoundary(section->GetSectionNumber()); + int section_size = 0xA4 - (0x48 - section->MaxVisibleSections) * sizeof(short); + section = reinterpret_cast( + reinterpret_cast(section) + section_size); + } while (section < last_section); + } + + pSectionD9 = FindDrivableSection(0x199); + pSectionC14 = FindDrivableSection(0x13A); + } else if (current_chunk_id == 0x34151) { + pInfo = reinterpret_cast(current_chunk->GetData()); + pInfo->EndianSwap(); + ScenerySectionLODOffset = pInfo->LODOffset; + } else if (current_chunk_id == 0x34154) { + } else if (current_chunk_id == 0x34155) { + LoadingSection *loading_sections = reinterpret_cast(current_chunk->GetData()); + int num_loading_sections = current_chunk->Size / sizeof(LoadingSection); + int n = 0; + while (n < num_loading_sections) { + LoadingSection *section = &loading_sections[n]; + LoadingSectionList.AddTail(section); + section->EndianSwap(); + n += 1; + } + } + + current_chunk = current_chunk->GetNext(); + } + + InitVisibleZones(); + RefreshTrackStreamer(); + return 1; + } + + if (chunk->GetID() == 0x34158) { + VisibleSectionOverlay *overlay = reinterpret_cast(chunk->GetData()); + OverlayList.AddTail(overlay); + overlay->EndianSwap(); + return 1; + } + + return 0; +} diff --git a/src/Speed/Indep/Src/World/VisibleSection.hpp b/src/Speed/Indep/Src/World/VisibleSection.hpp index 074d852b9..860d9f08b 100644 --- a/src/Speed/Indep/Src/World/VisibleSection.hpp +++ b/src/Speed/Indep/Src/World/VisibleSection.hpp @@ -9,6 +9,67 @@ #include "Speed/Indep/bWare/Inc/Strings.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern int ScenerySectionLODOffset; + +static inline char GetScenerySectionLetter(int section_number) { + return static_cast(section_number / 100 + 'A' - 1); +} + +static inline bool IsRegularScenerySection(int section_number) { + char section_letter = GetScenerySectionLetter(section_number); + return section_letter >= 'A' && section_letter < 'U'; +} + +static inline int GetScenerySubsectionNumber(int section_number) { + return section_number % 100; +} + +static inline short GetScenerySectionNumber(char section_letter, int subsection_number) { + return static_cast((section_letter - 'A' + 1) * 100 + subsection_number); +} + +static inline bool IsTextureSection(int section_number) { + char section_letter = GetScenerySectionLetter(section_number); + return section_letter == 'Y' || section_letter == 'W'; +} + +static inline bool IsLibrarySection(int section_number) { + char section_letter = GetScenerySectionLetter(section_number); + return section_letter == 'X' || section_letter == 'U'; +} + +static inline bool IsScenerySectionDrivable(int section_number) { + if (!IsRegularScenerySection(section_number)) { + return false; + } + + int subsection_number = GetScenerySubsectionNumber(section_number); + return subsection_number > 0 && subsection_number < ScenerySectionLODOffset; +} + +static inline int GetLODScenerySectionNumber(int drivable_section_number) { + return drivable_section_number + ScenerySectionLODOffset; +} + +static inline int bGetTablePos(short *table, int num_elements, short element) { + for (int n = 0; n < num_elements; n++) { + if (table[n] == element) { + return n; + } + } + + return -1; +} + +static inline bool bIsInTable(short *table, int num_elements, short element) { + return bGetTablePos(table, num_elements, element) >= 0; +} + +static inline bool HasSection(short *section_table, int num_sections, short section_number) { + return bIsInTable(section_table, num_sections, section_number); +} struct VisibleSectionBoundary : public bTNode { // total size: 0xA4 @@ -19,6 +80,20 @@ struct VisibleSectionBoundary : public bTNode { bVector2 BBoxMax; // offset 0x14, size 0x8 bVector2 Centre; // offset 0x1C, size 0x8 bVector2 Points[16]; // offset 0x24, size 0x80 + + void EndianSwap(); + bool IsPointInside(const bVector2 *point); + float GetDistanceOutside(const bVector2 *point, float max_distance); + int GetSectionNumber(); + int GetMemoryImageSize(); + + int GetNumPoints() { + return NumPoints; + } + + bVector2 *GetPoint(int n) { + return &Points[n]; + } }; struct VisibleSectionCoordinate { @@ -38,6 +113,20 @@ struct DrivableScenerySection : public bTNode { short VisibleSections[72]; // offset 0x12, size 0x90 short Padding; // offset 0xA2, size 0x2 + void EndianSwap(); + int GetSectionNumber() { + return SectionNumber; + } + int GetMemoryImageSize(); + void AddVisibleSection(int section_number); + int IsSectionVisible(int section_number); + void RemoveVisibleSection(int section_number); + void SortVisibleSections(); + + int GetNumVisibleSections() { + return this->NumVisibleSections; + } + int GetVisibleSection(int i) { return this->VisibleSections[i]; } @@ -47,6 +136,8 @@ struct DrivableSectionsInRegion { // total size: 0x324 int NumSections; // offset 0x0, size 0x4 short Sections[400]; // offset 0x4, size 0x320 + + void EndianSwap(); }; struct VisibleTextureSection : public bTNode { @@ -66,6 +157,8 @@ struct LoadingSection : public bTNode { short DrivableSections[16]; // offset 0x1A, size 0x20 short NumExtraSections; // offset 0x3A, size 0x2 short ExtraSections[8]; // offset 0x3C, size 0x10 + + void EndianSwap(); }; struct SuperScenerySection : public bTNode { @@ -112,14 +205,83 @@ struct VisibleSectionOverlay : public bTNode { char Name[40]; // offset 0x8, size 0x28 int NumEntries; // offset 0x30, size 0x4 OverlayEntry EntryTable[4096]; // offset 0x34, size 0x6000 + + void EndianSwap(); }; struct VisibleSectionManagerInfo { // total size: 0x328 int LODOffset; // offset 0x0, size 0x4 DrivableSectionsInRegion TheDrivableSectionsInRegion; // offset 0x4, size 0x324 + + void EndianSwap(); }; +inline void VisibleSectionBoundary::EndianSwap() { + bPlatEndianSwap(&SectionNumber); + bPlatEndianSwap(reinterpret_cast(&NumPoints)); + bPlatEndianSwap(reinterpret_cast(&PanoramaBoundary)); + bPlatEndianSwap(&BBoxMin); + bPlatEndianSwap(&BBoxMax); + for (int i = 0; i < NumPoints; i++) { + bPlatEndianSwap(&Points[i]); + } +} + +inline int VisibleSectionBoundary::GetSectionNumber() { + return SectionNumber; +} + +inline int VisibleSectionBoundary::GetMemoryImageSize() { + return 0xA4 - (16 - NumPoints) * sizeof(bVector2); +} + +inline void DrivableSectionsInRegion::EndianSwap() { + bPlatEndianSwap(&NumSections); + for (int i = 0; i < NumSections; i++) { + bPlatEndianSwap(&Sections[i]); + } +} + +inline void DrivableScenerySection::EndianSwap() { + bPlatEndianSwap(&SectionNumber); + bPlatEndianSwap(reinterpret_cast(&MostVisibleSections)); + bPlatEndianSwap(reinterpret_cast(&MaxVisibleSections)); + bPlatEndianSwap(&NumVisibleSections); + for (int i = 0; i < NumVisibleSections; i++) { + bPlatEndianSwap(&VisibleSections[i]); + } +} + +inline int DrivableScenerySection::GetMemoryImageSize() { + return 0x12 + NumVisibleSections * sizeof(short) + sizeof(Padding); +} + +inline void LoadingSection::EndianSwap() { + bPlatEndianSwap(&NumDrivableSections); + for (int i = 0; i < NumDrivableSections; i++) { + bPlatEndianSwap(&DrivableSections[i]); + } + bPlatEndianSwap(&NumExtraSections); + for (int i = 0; i < NumExtraSections; i++) { + bPlatEndianSwap(&ExtraSections[i]); + } +} + +inline void VisibleSectionOverlay::EndianSwap() { + bPlatEndianSwap(&NumEntries); + for (int n = 0; n < NumEntries; n++) { + OverlayEntry *entry = &EntryTable[n]; + bPlatEndianSwap(&entry->DrivableSectionNumber); + bPlatEndianSwap(&entry->SectionNumber); + } +} + +inline void VisibleSectionManagerInfo::EndianSwap() { + bPlatEndianSwap(&LODOffset); + TheDrivableSectionsInRegion.EndianSwap(); +} + struct OverrideSectionObject : public bTNode { // total size: 0x4C short SectionNumber; // offset 0x8, size 0x2 @@ -141,20 +303,63 @@ struct VisibleSectionUserInfo { struct UnallocatedVisibleSectionUserInfo {}; struct VisibleGroupInfo { + // total size: 0x8 char *SelectionSetName; // offset 0x0, size 0x4 bool UsedForTopology; // offset 0x4, size 0x1 }; -// total size: 0x6830 class VisibleSectionManager { public: static VisibleGroupInfo *GetGroupInfo(const char *selection_set_name); + VisibleTextureSection *FindVisibleTextureSection(int section_number); + + LoadingSection *FindLoadingSection(int drivable_section_number); + + LoadingSection *FindLoadingSection(const char *name); + + int GetSectionsToLoad(LoadingSection *loading_section, short *section_numbers, int max_sections); + + OverrideSectionObject *FindOverrideSectionObject(const char *name, OverrideSectionObject *prev_object, bool partial_compare); + + VisibleSectionManager(); + + ~VisibleSectionManager(); + + VisibleSectionUserInfo *AllocateUserInfo(int section_number); + void UnallocateUserInfo(int section_number); + + void ActivateOverlay(const char *name); + + void ActivateOverlay(VisibleSectionOverlay *overlay, VisibleSectionOverlay *undo_overlay); + + void UnactivateOverlay(); + + int Loader(bChunk *chunk); + + int Unloader(bChunk *chunk); + + VisibleSectionBoundary *FindBoundary(int section_number); + + VisibleSectionBoundary *FindClosestBoundary(const bVector2 *point, float *distance_outside); + + VisibleSectionBoundary *FindBoundary(const bVector2 *point); + + int FindCloseBoundaries(VisibleSectionBoundary **boundaries, int max_boundaries, const bVector2 *point, float distance_outside); + DrivableScenerySection *FindDrivableSection(const bVector2 *point); + DrivableScenerySection *FindDrivableSection(int section_number); + + unsigned int GetVisibleSectionChecksum(int section_number); + + bool IsGroupEnabled(unsigned int group_name_hash); + void EnableGroup(unsigned int group_name_hash); + void DisableGroup(unsigned int group_name_hash); + void DisableAllGroups() { bMemSet(EnabledGroups, 0, 0x400); } diff --git a/src/Speed/Indep/Src/World/WWorldPos.h b/src/Speed/Indep/Src/World/WWorldPos.h index b1c11652d..f2805c27a 100644 --- a/src/Speed/Indep/Src/World/WWorldPos.h +++ b/src/Speed/Indep/Src/World/WWorldPos.h @@ -44,7 +44,9 @@ class WWorldPos { // bool OffEdge() const {} - // bool OnValidFace() const {} + bool OnValidFace() const { + return fFaceValid; + } void ForceFaceValidity() {} diff --git a/src/Speed/Indep/Src/World/WeatherMan.cpp b/src/Speed/Indep/Src/World/WeatherMan.cpp index e69de29bb..4ecc6bc83 100644 --- a/src/Speed/Indep/Src/World/WeatherMan.cpp +++ b/src/Speed/Indep/Src/World/WeatherMan.cpp @@ -0,0 +1,275 @@ +#include "WeatherMan.hpp" + +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +bTList RegionLists[NUM_REGION_TYPES]; +int RegionCount[NUM_REGION_TYPES]; +bVector3 cPos; +extern float BaseWeatherFogStart; +extern float BaseWeatherFog; +extern float BaseFogFalloffY; +extern float BaseFogFalloffX; +extern float BaseFogFalloff; +extern float DAT_80409c34; +extern float DAT_80409c38; +extern float DAT_80409c3c; +extern float DAT_80409c40; +extern float DAT_80409c44; +extern int FogControlOverRide; +extern int BaseWeatherFogColourR; +extern int BaseWeatherFogColourG; +extern int BaseWeatherFogColourB; +extern float oldDistFogStart_27399; +extern float oldDistFogPower_27398; +extern unsigned int oldDistFogColour_27397; + +int RegionQuery::CalculateRegionInfo(eView *view, RegionType regionKind, int InFE) { + unsigned int colr_r = 0; + unsigned int colr_g = 0; + unsigned int colr_b = 0; + bVector3 cPos(*view->GetCamera()->GetPosition()); + + (void)InFE; + + if (FogControlOverRide) { + unsigned int retcol; + unsigned int fog_colour = BaseWeatherFogColourB << 16 | BaseWeatherFogColourG << 8 | BaseWeatherFogColourR; + + FogFalloff = BaseFogFalloff; + FogFalloffX = BaseFogFalloffX; + FogFalloffY = BaseFogFalloffY; + DistFogStart = BaseWeatherFogStart; + DistFogPower = BaseWeatherFog; + retcol = fog_colour | 0x80000000; + DistFogColour = retcol; + if (oldDistFogColour_27397 == retcol) { + if (oldDistFogPower_27398 == DistFogPower) { + if (oldDistFogStart_27399 == DistFogStart) { + return 0; + } + } + } + } else { + DistFogPower = DAT_80409c34; + DistFogStart = DAT_80409c34; + FogFalloffY = DAT_80409c34; + FogFalloffX = DAT_80409c34; + FogFalloff = DAT_80409c34; + float smallest = DAT_80409c38; + bTList *region_list = &RegionLists[static_cast(regionKind)]; + + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + bVector4 direction; + direction.x = region->PositionX - cPos.x; + direction.y = region->PositionY - cPos.y; + float distanceSq = direction.x * direction.x + direction.y * direction.y; + + if (distanceSq < region->Radius * region->Radius) { + float distance = DAT_80409c34; + if (DAT_80409c3c < distanceSq) { + distance = bSqrt(distanceSq); + } + + float blend; + if (distance < region->FarFalloffStart) { + blend = DAT_80409c34; + } else { + blend = (distance - region->FarFalloffStart) / (region->Radius - region->FarFalloffStart); + } + + region->effect = DAT_80409c44 - blend; + if (blend == DAT_80409c34) { + region->inFlags = 1; + if (region->Radius < smallest) { + smallest = region->Radius; + } + } else { + region->inFlags = 2; + } + } else { + region->inFlags = 4; + region->effect = DAT_80409c34; + } + } + + float totaleffex = DAT_80409c34; + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + region->effect = region->effect * region->modifier; + totaleffex += region->effect; + } + + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + if (totaleffex == DAT_80409c34) { + region->effect = DAT_80409c34; + } else { + region->effect = region->effect / totaleffex; + } + + if (region->effect != DAT_80409c34) { + FogFalloff += region->FogFalloff * region->effect; + FogFalloffX += region->FogFalloffX * region->effect; + FogFalloffY += region->FogFalloffY * region->effect; + DistFogStart += region->FogStart * region->effect; + DistFogPower += region->Intensity * region->effect; + + unsigned int fog_colour = region->FogColour; + unsigned int fog_colour_r = static_cast(fog_colour); + unsigned int fog_colour_g = static_cast(fog_colour >> 8); + unsigned int fog_colour_b = static_cast(fog_colour >> 16); + + colr_r += static_cast(fog_colour_r * region->effect); + colr_g += static_cast(fog_colour_g * region->effect); + colr_b += static_cast(fog_colour_b * region->effect); + } + } + + unsigned int retcol = colr_r | colr_g << 8 | colr_b << 16 | 0x80000000; + DistFogColour = retcol; + if (oldDistFogColour_27397 == retcol) { + if (oldDistFogPower_27398 == DistFogPower) { + if (oldDistFogStart_27399 == DistFogStart) { + return 0; + } + } + } + } + + oldDistFogColour_27397 = DistFogColour; + oldDistFogPower_27398 = DistFogPower; + oldDistFogStart_27399 = DistFogStart; + return 1; +} + +int LoaderWeatherMan(bChunk *bchunk) { + if (bchunk->GetID() != 0x34250) { + return 0; + } + + unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); + bEndianSwap32(data + 8); + bEndianSwap32(data + 0xC); + if (*reinterpret_cast(data + 8) == 2) { + int num_regions = *reinterpret_cast(data + 0xC); + unsigned char *region_data = data + 0x10; + for (int i = 0; i < num_regions; i++) { + bEndianSwap32(region_data + 0x84); + bEndianSwap32(region_data + 0x88); + bEndianSwap32(region_data + 0x48); + bEndianSwap32(region_data + 0x4C); + bEndianSwap32(region_data + 0x50); + bEndianSwap32(region_data + 0x54); + bEndianSwap32(region_data + 0x58); + bEndianSwap32(region_data + 0x5C); + bEndianSwap32(region_data + 0x60); + bEndianSwap32(region_data + 0x64); + bEndianSwap32(region_data + 0x68); + bEndianSwap32(region_data + 0x6C); + bEndianSwap32(region_data + 0x70); + bEndianSwap32(region_data + 0x74); + bEndianSwap32(region_data + 0x78); + bEndianSwap32(region_data + 0x8C); + bEndianSwap32(region_data + 0x90); + bEndianSwap32(region_data + 0x94); + AddRegion(reinterpret_cast(region_data)); + region_data += sizeof(GenericRegion); + } + } + + return 1; +} + +int UnloaderWeatherMan(bChunk *bchunk) { + if (bchunk->GetID() != 0x34250) { + return 0; + } + + unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); + int version = *reinterpret_cast(data + 8); + if (version == 2) { + GenericRegion *region = reinterpret_cast(data + 0x10); + int num_regions = *reinterpret_cast(data + 0xC); + for (int i = 0; i < num_regions; i++) { + RemoveRegion(region); + region += 1; + } + } + + return 1; +} + +void AddRegion(GenericRegion *region) { + unsigned int region_type = static_cast(region->Type); + if (region_type == REGION_RAIN && region->Intensity == 0.0f) { + region->Type = REGION_TUNNEL; + region_type = REGION_TUNNEL; + } + + if (region_type < NUM_REGION_TYPES) { + RegionLists[region_type].AddTail(region); + RegionCount[region_type] += 1; + } +} + +void RemoveRegion(GenericRegion *region) { + region->Remove(); +} + +int DepthRegion(GenericRegion *before, GenericRegion *after) { + bVector3 Position(before->PositionX, before->PositionY, before->PositionZ); + bVector3 Delta = Position - cPos; + float distB = bLength(Delta); + Position = bVector3(after->PositionX, after->PositionY, after->PositionZ); + Delta = Position - cPos; + float distA = bLength(Delta); + return distB <= distA; +} + +GenericRegion *GetClosestRegionInView(eView *view, bVector3 *endVector, float *angleCos) { + cPos = *view->GetCamera()->GetPosition(); + bVector3 cDir(*view->GetCamera()->GetDirection()); + bTList *region_list = &RegionLists[REGION_BLOOM]; + region_list->Sort(DepthRegion); + bVector3 posScreen; + + CameraMover *cameraMover = view->GetCameraMover(); + if (!cameraMover) { + return 0; + } + + CameraAnchor *cameraAnchor = cameraMover->GetAnchor(); + if (!cameraAnchor) { + return 0; + } + + bVector3 MyCarPos(*cameraAnchor->GetGeometryPosition()); + float maxDist = 99999.0f; + float angleCOS; + GenericRegion *ClosestRegion = 0; + + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + bVector3 Position(region->PositionX, region->PositionY, region->PositionZ); + bVector3 Delta = Position - cPos; + bVector3 Delta2(Delta); + bNormalize(&Delta2, &Delta); + + angleCOS = bDot(Delta2, cDir); + if (0.0f < angleCOS) { + float dist = bLength(Delta); + if (dist < maxDist) { + *angleCos = angleCOS; + ClosestRegion = region; + maxDist = dist; + } + } + } + + if (ClosestRegion) { + endVector->x = ClosestRegion->PositionX; + endVector->y = ClosestRegion->PositionY; + endVector->z = ClosestRegion->PositionZ; + return ClosestRegion; + } + return 0; +} diff --git a/src/Speed/Indep/Src/World/WeatherMan.hpp b/src/Speed/Indep/Src/World/WeatherMan.hpp index f0d76ccbf..7f38f3c03 100644 --- a/src/Speed/Indep/Src/World/WeatherMan.hpp +++ b/src/Speed/Indep/Src/World/WeatherMan.hpp @@ -66,4 +66,10 @@ struct RegionQuery { int CalculateRegionInfo(eView *view, RegionType regionKind, int InFE); }; +void AddRegion(GenericRegion *region); +void RemoveRegion(GenericRegion *region); +int DepthRegion(GenericRegion *before, GenericRegion *after); +GenericRegion *GetClosestRegionInView(eView *view, bVector3 *endVector, float *angleCos); +int UnloaderWeatherMan(bChunk *bchunk); + #endif diff --git a/src/Speed/Indep/bWare/Inc/Espresso.hpp b/src/Speed/Indep/bWare/Inc/Espresso.hpp new file mode 100644 index 000000000..ae3da5637 --- /dev/null +++ b/src/Speed/Indep/bWare/Inc/Espresso.hpp @@ -0,0 +1,14 @@ +#ifndef BWARE_ESPRESSO_H +#define BWARE_ESPRESSO_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +inline void espEmptyLayer(const char *layername) {} + +inline int espGetLayerState(const char *layername) { + return 0; +} + +#endif diff --git a/src/Speed/Indep/bWare/Inc/bList.hpp b/src/Speed/Indep/bWare/Inc/bList.hpp index 70079e26d..35b702e57 100644 --- a/src/Speed/Indep/bWare/Inc/bList.hpp +++ b/src/Speed/Indep/bWare/Inc/bList.hpp @@ -36,8 +36,8 @@ class bNode { } bNode *AddAfter(bNode *insert_point) { - bNode *new_next = insert_point->Next; bNode *new_prev = this->Prev; // unused + bNode *new_next = insert_point->Next; insert_point->Next = this; new_next->Prev = this; this->Prev = insert_point; @@ -315,8 +315,10 @@ class bPNode : public bTNode { this->Object = object; } - bPNode *GetObject() { - return reinterpret_cast(Object); + ~bPNode() {} + + void *GetObject() { + return Object; } void *GetpObject() { @@ -347,7 +349,7 @@ template class bPList : public bTList { void Remove(bNode *node) { bList::Remove(node); - delete node; + delete reinterpret_cast(node); } void RemoveHead() { diff --git a/src/Speed/Indep/bWare/Inc/bMath.hpp b/src/Speed/Indep/bWare/Inc/bMath.hpp index 67c82ebef..ed40fd9a8 100644 --- a/src/Speed/Indep/bWare/Inc/bMath.hpp +++ b/src/Speed/Indep/bWare/Inc/bMath.hpp @@ -243,7 +243,7 @@ struct bVector2 { int operator==(const bVector2 &v); - // bVector2 &operator=(const bVector2 &v) {} // compiler generated? shown in dwarf + bVector2 &operator=(const bVector2 &v); // bVector2(const bVector2 &v) {} // compiler generated @@ -262,6 +262,18 @@ inline bVector2 *bFill(bVector2 *dest, float x, float y) { return dest; } +inline bVector2 *bCopy(bVector2 *dest, const bVector2 *v) { + float x = v->x; + float y = v->y; + bFill(dest, x, y); + return dest; +} + +inline bVector2 &bVector2::operator=(const bVector2 &v) { + bCopy(this, &v); + return *this; +} + inline bVector2::bVector2(float _x, float _y) { bFill(this, _x, _y); } @@ -286,12 +298,35 @@ inline bVector2 bVector2::operator-(const bVector2 &v) const { return bVector2(_x, _y); } +inline bVector2 *bScale(bVector2 *dest, const bVector2 *v, float scale) { + float x = v->x; + float y = v->y; + + dest->x = x * scale; + dest->y = y * scale; + return dest; +} + +inline bVector2 bScale(const bVector2 &v, float scale) { + bVector2 dest; + bScale(&dest, &v, scale); + return dest; +} + +inline bVector2 bVector2::operator*(float f) const { + return bScale(*this, f); +} + inline float bLength(const bVector2 *v) { float x = v->x; float y = v->y; return bSqrt(x * x + y * y); } +inline float bLength(const bVector2 &v) { + return bLength(&v); +} + inline bVector2 bNormalize(const bVector2 &v) { bVector2 dest; bNormalize(&dest, &v); @@ -304,15 +339,6 @@ inline float bDot(const bVector2 *v1, const bVector2 *v2) { return v1->x * v2->x + v1->y * v2->y; } -inline bVector2 *bScale(bVector2 *dest, const bVector2 *v, float scale) { - float x = v->x; - float y = v->y; - - dest->x = x * scale; - dest->y = y * scale; - return dest; -} - struct ALIGN_16 bVector3 { // total size: 0x10 float x; // offset 0x0, size 0x4 @@ -930,15 +956,25 @@ struct bQuaternion { class bBitTable { public: - // bBitTable() {} + bBitTable() { + Bits = 0; + NumBits = 0; + } - // bBitTable(void *mem, int num_bits) {} + bBitTable(void *mem, int num_bits) { + Init(mem, num_bits); + } - // void Init(void *mem, int num_bits) {} + void Init(void *mem, int num_bits) { + Bits = reinterpret_cast(mem); + NumBits = num_bits; + } - // void ClearTable() {} + void ClearTable(); - // void Set(int bit) {} + void Set(int bit) { + Bits[bit >> 3] |= static_cast(1 << (bit & 7)); + } // void Clear(int bit) {} diff --git a/src/Speed/Indep/bWare/Inc/bMemory.hpp b/src/Speed/Indep/bWare/Inc/bMemory.hpp index 5be513d4d..fccc94a4a 100644 --- a/src/Speed/Indep/bWare/Inc/bMemory.hpp +++ b/src/Speed/Indep/bWare/Inc/bMemory.hpp @@ -147,18 +147,4 @@ void bMemorySetOverflowPoolNumber(int pool_num, int overflow_pool_number); void *bWareMalloc(int size, const char *debug_text, int debug_line, int allocation_params); -inline int bMemoryGetPoolNum(int allocation_params) { - return allocation_params & 0xf; -} - -inline int bMemoryGetAlignment(int allocation_params) { - int alignment = allocation_params >> 6 & 0x1ffc; - - return alignment; -} - -inline int bMemoryGetAlignmentOffset(int allocation_params) { - return (allocation_params >> 17) & 0x1ffc; -} - #endif diff --git a/src/Speed/Indep/bWare/Inc/bWare.hpp b/src/Speed/Indep/bWare/Inc/bWare.hpp index 28202994b..7f74728d5 100644 --- a/src/Speed/Indep/bWare/Inc/bWare.hpp +++ b/src/Speed/Indep/bWare/Inc/bWare.hpp @@ -133,4 +133,26 @@ inline int bIsBFunkAvailable() { unsigned int bCalculateCrc32(const void *data, int size, unsigned int prev_crc32); +inline int bMemoryGetPoolNum(int allocation_params) { + return allocation_params & 0xf; +} + +inline int bMemoryGetAlignment(int allocation_params) { + int alignment = allocation_params >> 6 & 0x1ffc; + + return alignment; +} + +inline int bMemoryGetBestFit(int allocation_params) { + return allocation_params & 0x80; +} + +inline int bMemoryGetTopBit(int allocation_params) { + return allocation_params & 0x40; +} + +inline int bMemoryGetAlignmentOffset(int allocation_params) { + return (allocation_params >> 17) & 0x1ffc; +} + #endif From 86c77a4583088dfe825e31bd7514c9c4b5bb8e4a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:42:53 +0100 Subject: [PATCH 086/372] 70.5%: decode singleq physical and delta per case Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 0da29aa1d..4116f770a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -114,18 +114,20 @@ static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, D } static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { - q.x = kSingleQFloatZero; - q.y = kSingleQFloatZero; - q.z = kSingleQFloatZero; - q.w = physical.mW * kSingleQRangeScale8Bit - kSingleQFloatOne; - if (index == 0) { q.x = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + q.y = kSingleQFloatZero; + q.z = kSingleQFloatZero; } else if (index == 1) { + q.x = kSingleQFloatZero; q.y = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + q.z = kSingleQFloatZero; } else { + q.x = kSingleQFloatZero; + q.y = kSingleQFloatZero; q.z = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; } + q.w = physical.mW * kSingleQRangeScale8Bit - kSingleQFloatOne; } static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, const DeltaSingleQDelta &delta, UMath::Vector4 &q) { @@ -138,18 +140,20 @@ static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, cons v = minRangef.mRange[0] * delta.mV + minRangef.mMin[0]; w = minRangef.mRange[1] * delta.mW + minRangef.mMin[1]; - q.x = kSingleQFloatZero; - q.y = kSingleQFloatZero; - q.z = kSingleQFloatZero; - q.w = w; - if (minRangef.mIndex == 0) { q.x = v; + q.y = kSingleQFloatZero; + q.z = kSingleQFloatZero; } else if (minRangef.mIndex == 1) { + q.x = kSingleQFloatZero; q.y = v; + q.z = kSingleQFloatZero; } else { + q.x = kSingleQFloatZero; + q.y = kSingleQFloatZero; q.z = v; } + q.w = w; } static inline void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, From 840963ac73b4e8076eb1ac0455bccdf51d6c0c7c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:46:17 +0100 Subject: [PATCH 087/372] 70.6%: restore singleq physical zero-then-axis decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 4116f770a..9ddcdb7cb 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -114,17 +114,15 @@ static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, D } static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { + q.x = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.z = kSingleQFloatZero; + if (index == 0) { q.x = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; - q.y = kSingleQFloatZero; - q.z = kSingleQFloatZero; } else if (index == 1) { - q.x = kSingleQFloatZero; q.y = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; - q.z = kSingleQFloatZero; } else { - q.x = kSingleQFloatZero; - q.y = kSingleQFloatZero; q.z = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; } q.w = physical.mW * kSingleQRangeScale8Bit - kSingleQFloatOne; From f22d0ef6d14e44feb6e3f626c41284a3d157dc6f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:47:10 +0100 Subject: [PATCH 088/372] 70.7%: restore singleq delta zero-then-axis decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 9ddcdb7cb..029b10e59 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -138,17 +138,15 @@ static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, cons v = minRangef.mRange[0] * delta.mV + minRangef.mMin[0]; w = minRangef.mRange[1] * delta.mW + minRangef.mMin[1]; + q.x = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.z = kSingleQFloatZero; + if (minRangef.mIndex == 0) { q.x = v; - q.y = kSingleQFloatZero; - q.z = kSingleQFloatZero; } else if (minRangef.mIndex == 1) { - q.x = kSingleQFloatZero; q.y = v; - q.z = kSingleQFloatZero; } else { - q.x = kSingleQFloatZero; - q.y = kSingleQFloatZero; q.z = v; } q.w = w; From 98433036024d046721cf272f984365a9bde615af Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:48:28 +0100 Subject: [PATCH 089/372] 70.7%: reverse singleq decoder zero-store order Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 029b10e59..ba2e75fb7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -114,9 +114,9 @@ static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, D } static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { - q.x = kSingleQFloatZero; - q.y = kSingleQFloatZero; q.z = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.x = kSingleQFloatZero; if (index == 0) { q.x = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; @@ -138,9 +138,9 @@ static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, cons v = minRangef.mRange[0] * delta.mV + minRangef.mMin[0]; w = minRangef.mRange[1] * delta.mW + minRangef.mMin[1]; - q.x = kSingleQFloatZero; - q.y = kSingleQFloatZero; q.z = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.x = kSingleQFloatZero; if (minRangef.mIndex == 0) { q.x = v; From 54453511269161741accd7bc05b7e0eba1d9db8a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:49:51 +0100 Subject: [PATCH 090/372] 70.8%: fold singleq min-range scale constant Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index ba2e75fb7..e697450c3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -16,6 +16,7 @@ static const float kSingleQAngleScale16Bit = 9.5875265e-5f; static const float kSingleQRangeScale16Bit = 3.0518044e-5f; static const float kSingleQRangeScale8Bit = 7.8431377e-3f; static const float kSingleQRangeScale4Bit = 0.13333334f; +static const float kSingleQRangeScale16To4Bit = 8.1381455e-6f; static inline int GetSingleQFrameDeltaSize(const DeltaSingleQ *deltaQ) { return deltaQ->mNumBones * sizeof(DeltaSingleQDelta); @@ -106,8 +107,8 @@ static inline void SingleQQuatMultQxZ(const UMath::Vector4 &a, const UMath::Vect static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSingleQMinRangef &minRangef) { minRangef.mMin[0] = minRange.mMin[0] * kSingleQRangeScale16Bit - kSingleQFloatOne; minRangef.mMin[1] = minRange.mMin[1] * kSingleQRangeScale16Bit - kSingleQFloatOne; - minRangef.mRange[0] = 2.0f * (minRange.mRange[0] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; - minRangef.mRange[1] = 2.0f * (minRange.mRange[1] * kSingleQRangeScale16Bit) * kSingleQRangeScale4Bit; + minRangef.mRange[0] = minRange.mRange[0] * kSingleQRangeScale16To4Bit; + minRangef.mRange[1] = minRange.mRange[1] * kSingleQRangeScale16To4Bit; minRangef.mIndex = static_cast(minRange.mIndex); minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit + -kSingleQPi; minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit + -kSingleQPi; From 9a75fed09d0dc8f60c2bdd6800cbefabe9d3991f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:51:52 +0100 Subject: [PATCH 091/372] 70.8%: hoist singleq delta index local Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index e697450c3..34e26c804 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -133,19 +133,21 @@ static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, cons DeltaSingleQMinRangef minRangef; float v = minRangef.mMin[0]; float w = minRangef.mMin[1]; + unsigned char index; DecodeSingleQMinRange(minRange, minRangef); v = minRangef.mRange[0] * delta.mV + minRangef.mMin[0]; w = minRangef.mRange[1] * delta.mW + minRangef.mMin[1]; + index = minRangef.mIndex; q.z = kSingleQFloatZero; q.y = kSingleQFloatZero; q.x = kSingleQFloatZero; - if (minRangef.mIndex == 0) { + if (index == 0) { q.x = v; - } else if (minRangef.mIndex == 1) { + } else if (index == 1) { q.y = v; } else { q.z = v; From 7b9063733d577933bce33b038507d72d87e10ed4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 08:56:11 +0100 Subject: [PATCH 092/372] 71.9%: reuse shared quat multiply helpers in singleq Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 34e26c804..0810e3d88 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -4,6 +4,10 @@ #include "Speed/Indep/Src/EAGL4Anim/AnimTypeId.h" #include "Speed/Indep/Src/EAGL4Anim/AnimUtil.h" +void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result); +void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); + namespace EAGL4Anim { namespace { @@ -158,11 +162,11 @@ static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, cons static inline void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, const UMath::Vector4 &post, UMath::Vector4 &result) { if (index == 0) { - SingleQQuatMultXxQ(mid, post, result); + QuatMultXxQ(mid, post, result); } else if (index == 1) { - SingleQQuatMultXxYxZ(pre, mid, post, result); + QuatMultXxYxZ(pre, mid, post, result); } else { - SingleQQuatMultQxZ(pre, mid, result); + QuatMultQxZ(pre, mid, result); } } From 74480d3311faaea4e3f5331514a231e1995b3d75 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:01:53 +0100 Subject: [PATCH 093/372] 72.0%: fold deltaq min-range scale constants Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 067e3d0f8..bd093e340 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -32,6 +32,8 @@ static const float kRangeScale16Bit = 3.0518044e-5f; static const float kRangeScale15Bit = 6.1037019e-5f; static const float kRangeScale8Bit = 7.8431377e-3f; static const float kRangeScale7Bit = 1.5748032e-2f; +static const float kRangeScale16To7Bit = 9.6119827e-7f; +static const float kRangeScale16To8Bit = 4.7871444e-7f; static inline int GetFrameDeltaSize(const DeltaQ *deltaQ) { return deltaQ->mNumBones * sizeof(DeltaQDelta); @@ -115,9 +117,9 @@ static inline void DecodeMinRange(const DeltaQMinRange &minRange, DeltaQMinRange minRangef.mMin.y = minRange.mMin[1] * kRangeScale16Bit - kFloatOne; minRangef.mMin.z = minRange.mMin[2] * kRangeScale16Bit - kFloatOne; - minRangef.mRange.x = 2.0f * (minRange.mRange[0] * kRangeScale16Bit) * kRangeScale7Bit; - minRangef.mRange.y = 2.0f * (minRange.mRange[1] * kRangeScale16Bit) * kRangeScale8Bit; - minRangef.mRange.z = 2.0f * (minRange.mRange[2] * kRangeScale16Bit) * kRangeScale8Bit; + minRangef.mRange.x = minRange.mRange[0] * kRangeScale16To7Bit; + minRangef.mRange.y = minRange.mRange[1] * kRangeScale16To8Bit; + minRangef.mRange.z = minRange.mRange[2] * kRangeScale16To8Bit; } static inline void DecodeDelta(const DeltaQMinRange &minRange, const DeltaQDelta &delta, UMath::Vector4 &q) { From 5cecd4bd41c5e8c437c62d5bb83d14069350d7cb Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:12:51 +0100 Subject: [PATCH 094/372] 72.3%: split pose blender masked loop Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index b92379f5c..424746b51 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -175,17 +175,26 @@ void FnPoseBlender::operator delete(void *ptr, size_t size) { void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float *pose1, float *result, const BoneMask *boneMask) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - if (boneMask && !boneMask->GetBone(boneIdx)) { - continue; - } + if (!boneMask) { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + int poseIdx = boneIdx * 12; - int poseIdx = boneIdx * 12; + FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); + result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); + result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); + result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); + } + } else { + for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + int poseIdx = boneIdx * 12; - FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); - result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); - result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); - result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); + FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); + result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); + result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); + result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); + } + } } } From 8b11d3a3bfe4d4c422ca491deee6c066966caca0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:14:44 +0100 Subject: [PATCH 095/372] 72.5%: inline euler interp helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp | 46 +++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp index 81015e8c8..b8ace0da6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp @@ -566,9 +566,49 @@ void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { // TODO inline and move void EulF3Interp(float w, float *&data0, float *&data1, float *output) { - EulF3(data0, qt0); - EulF3(data1, output); - FastQuatBlendF4(w, qt0, output, output); + const float degreesToRadians = 0.017453294f; + + float x = *data0++ * degreesToRadians * 0.5f; + float y = *data0++ * degreesToRadians * 0.5f; + float z = *data0++ * degreesToRadians * 0.5f; + float cx = cosf(x); + float cy = cosf(y); + float cz = cosf(z); + float sx = sinf(x); + float sy = sinf(y); + float sz = sinf(z); + + qt0[0] = cy * sx * cz - sy * cx * sz; + qt0[2] = cy * cx * sz - sy * sx * cz; + qt0[1] = cy * sx * sz + sy * cx * cz; + qt0[3] = cy * cx * cz + sy * sx * sz; + + x = *data1++ * degreesToRadians * 0.5f; + y = *data1++ * degreesToRadians * 0.5f; + z = *data1++ * degreesToRadians * 0.5f; + cx = cosf(x); + cy = cosf(y); + cz = cosf(z); + sx = sinf(x); + sy = sinf(y); + sz = sinf(z); + + output[0] = cy * sx * cz - sy * cx * sz; + output[2] = cy * cx * sz - sy * sx * cz; + output[1] = cy * sx * sz + sy * cx * cz; + output[3] = cy * cx * cz + sy * sx * sz; + + if (qt0[0] * output[0] + qt0[1] * output[1] + qt0[2] * output[2] + qt0[3] * output[3] > 0.0f) { + output[0] = w * (output[0] - qt0[0]) + qt0[0]; + output[1] = w * (output[1] - qt0[1]) + qt0[1]; + output[2] = w * (output[2] - qt0[2]) + qt0[2]; + output[3] = w * (output[3] - qt0[3]) + qt0[3]; + } else { + output[0] = qt0[0] - w * (output[0] + qt0[0]); + output[1] = qt0[1] - w * (output[1] + qt0[1]); + output[2] = qt0[2] - w * (output[2] + qt0[2]); + output[3] = qt0[3] - w * (output[3] + qt0[3]); + } } // TODO inline and move From c6fa107268bb50c9916bf585ddffbb15a33dd370 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:15:49 +0100 Subject: [PATCH 096/372] 72.6%: inline quat interp helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp | 31 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp index b8ace0da6..1c10d5c44 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp @@ -559,9 +559,34 @@ void TranF3(float *&data, float *output) { // TODO inline and move void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { - QuatF4(data0, qt0); - QuatF4(data1, output); - FastQuatBlendF4(w, qt0, output, output); + qt0[0] = *data0++; + qt0[1] = *data0++; + qt0[2] = *data0++; + qt0[3] = *data0++; + + output[0] = *data1++; + output[1] = *data1++; + output[2] = *data1++; + output[3] = *data1++; + + if (qt0[0] * output[0] + qt0[1] * output[1] + qt0[2] * output[2] + qt0[3] * output[3] > 0.0f) { + output[0] = w * (output[0] - qt0[0]) + qt0[0]; + output[1] = w * (output[1] - qt0[1]) + qt0[1]; + output[2] = w * (output[2] - qt0[2]) + qt0[2]; + output[3] = w * (output[3] - qt0[3]) + qt0[3]; + } else { + output[0] = qt0[0] - w * (output[0] + qt0[0]); + output[1] = qt0[1] - w * (output[1] + qt0[1]); + output[2] = qt0[2] - w * (output[2] + qt0[2]); + output[3] = qt0[3] - w * (output[3] + qt0[3]); + } + + float invNorm = 1.0f / sqrtf(output[0] * output[0] + output[1] * output[1] + output[2] * output[2] + output[3] * output[3]); + + output[0] = output[0] * invNorm; + output[3] = output[3] * invNorm; + output[1] = output[1] * invNorm; + output[2] = output[2] * invNorm; } // TODO inline and move From 2f938d645859b67ea6b3d79f6df51c49107b455b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:18:25 +0100 Subject: [PATCH 097/372] 72.7%: normalize euler interp helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp index 1c10d5c44..ff8f0ade3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp @@ -634,6 +634,13 @@ void EulF3Interp(float w, float *&data0, float *&data1, float *output) { output[2] = qt0[2] - w * (output[2] + qt0[2]); output[3] = qt0[3] - w * (output[3] + qt0[3]); } + + float invNorm = 1.0f / sqrtf(output[0] * output[0] + output[1] * output[1] + output[2] * output[2] + output[3] * output[3]); + + output[0] = output[0] * invNorm; + output[3] = output[3] * invNorm; + output[1] = output[1] * invNorm; + output[2] = output[2] * invNorm; } // TODO inline and move From 6d66a0b1a2b4098525c8e0d75c6f7137e7a06e9d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:21:03 +0100 Subject: [PATCH 098/372] 72.9%: recover cycle wrapper stubs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnCycle.h | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h index 37366dac1..9db6fe7f1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h @@ -42,30 +42,53 @@ class FnCycle : public FnAnim { mpAnim = anim; mStartTime = startTime; mEndTime = endTime; + mLength = endTime - startTime; } FnAnim *GetAnim() { return mpAnim; } - float GetStartTime() const {} + float GetStartTime() const { + return mStartTime; + } - float GetEndTime() const {} + float GetEndTime() const { + return mEndTime; + } // Overrides: FnAnim - void Eval(float previousTime, float currentTime, float *dofs) override {} + void Eval(float previousTime, float currentTime, float *dofs) override { + mpAnim->Eval(GetInRangeTime(previousTime), GetInRangeTime(currentTime), dofs); + } // Overrides: FnAnim - bool EvalEvent(float previousTime, float currentTime, EventHandler **eventHandlers, void *extraData) override {} + bool EvalEvent(float previousTime, float currentTime, EventHandler **eventHandlers, void *extraData) override { + return mpAnim->EvalEvent(GetInRangeTime(previousTime), GetInRangeTime(currentTime), eventHandlers, extraData); + } // Overrides: FnAnim - bool EvalSQT(float currentTime, float *sqt, const BoneMask *boneMask) override {} + bool EvalSQT(float currentTime, float *sqt, const BoneMask *boneMask) override { + return mpAnim->EvalSQT(GetInRangeTime(currentTime), sqt, boneMask); + } // Overrides: FnAnim - bool EvalPhase(float currentTime, PhaseValue &phase) override {} + bool EvalPhase(float currentTime, PhaseValue &phase) override { + return mpAnim->EvalPhase(GetInRangeTime(currentTime), phase); + } private: - float GetInRangeTime(float t) const {} + float GetInRangeTime(float t) const { + if (t < mStartTime) { + return mEndTime - ((t - mStartTime) - static_cast(static_cast((t - mStartTime) / mLength)) * mLength); + } else { + if (t <= mEndTime) { + return t; + } + t = t - mEndTime; + return mStartTime + (t - static_cast(static_cast(t / mLength)) * mLength); + } + } float mStartTime; // offset 0xC, size 0x4 float mEndTime; // offset 0x10, size 0x4 From f73f4faef12ecd2e9d81c22ec4f21e430545a8d3 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:22:26 +0100 Subject: [PATCH 099/372] 73.1%: recover raw linear channel eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRawLinearChannel.h | 4 +- .../Indep/Src/EAGL4Anim/RawLinearChannel.h | 80 ++++++++++++++++--- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h index 08a87ddea..c5c9ec22f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h @@ -56,7 +56,9 @@ class FnRawLinearChannel : public FnAnimMemoryMap { } // Overrides: FnAnim - void Eval(float, float currentTime, float *output) override {} + void Eval(float, float currentTime, float *output) override { + GetRawLinearChannel()->Eval(currentTime, output, mInterp); + } // Overrides: FnAnim bool GetLength(float &l) const override { diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h index ca721f3e5..fa3d6d3e9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h @@ -28,30 +28,86 @@ class RawLinearChannel : public AnimMemoryMap { mNumFrames = n; } - unsigned short *GetDOFIndex() {} + unsigned short *GetDOFIndex() { + return reinterpret_cast(&this[1]); + } - const unsigned short *GetDOFIndex() const {} + const unsigned short *GetDOFIndex() const { + return reinterpret_cast(&this[1]); + } - int GetDOFIndexSize() const {} + int GetDOFIndexSize() const { + return (mNumDOFs + 1) & ~1; + } - float *GetAnimData() {} + float *GetAnimData() { + return reinterpret_cast(GetDOFIndex() + GetDOFIndexSize()); + } - const float *GetAnimData() const {} + const float *GetAnimData() const { + return reinterpret_cast(GetDOFIndex() + GetDOFIndexSize()); + } - float *GetFrame(int i) {} + float *GetFrame(int i) { + return &GetAnimData()[i * mNumDOFs]; + } - const float *GetFrame(int i) const {} + const float *GetFrame(int i) const { + return &GetAnimData()[i * mNumDOFs]; + } - int GetSize() const {} + int GetSize() const { + return ComputeSize(mNumDOFs, mNumFrames); + } + + static int ComputeSize(int numDOFs, int numFrames) { + return sizeof(RawLinearChannel) + (((numDOFs + 1) & ~1) * sizeof(unsigned short)) + numFrames * numDOFs * sizeof(float); + } - static int ComputeSize(int numDOFs, int numFrames) {} + void EvalInterpFrame(float t, int frame0, int frame1, float *output) { + const unsigned short *dofIndex = GetDOFIndex(); + const float *frameData0 = GetFrame(frame0); + const float *frameData1 = GetFrame(frame1); + unsigned short numDOFs = mNumDOFs; - void EvalInterpFrame(float t, int frame0, int frame1, float *output) {} + for (int i = 0; i < numDOFs; i++) { + float value0 = *frameData0++; + output[*dofIndex++] = t * (*frameData1++ - value0) + value0; + } + } - void Eval(float frameTime, float *output, bool interp) {} + void Eval(float frameTime, float *output, bool interp) { + int frame = static_cast(frameTime); + + if (frame < 0) { + EvalFrame(0, output); + } else { + int lastFrame = mNumFrames - 1; + + if (frame < lastFrame) { + float t = frameTime - static_cast(frame); + + if (t == 0.0f || !interp) { + EvalFrame(frame, output); + } else { + EvalInterpFrame(t, frame, frame + 1, output); + } + } else { + EvalFrame(lastFrame, output); + } + } + } private: - void EvalFrame(int frame, float *output) {} + void EvalFrame(int frame, float *output) { + const unsigned short *dofIndex = GetDOFIndex(); + const float *frameData = GetFrame(frame); + unsigned short numDOFs = mNumDOFs; + + for (int i = 0; i < numDOFs; i++) { + output[*dofIndex++] = *frameData++; + } + } unsigned short mNumDOFs; // offset 0x4, size 0x2 unsigned short mNumFrames; // offset 0x6, size 0x2 From f89da7dde2823614251e79465772ae563d6df90c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:23:55 +0100 Subject: [PATCH 100/372] 73.4%: restore blender cycle time arithmetic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 13 +++++-------- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 13 +++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 3f8d00c7a..95ca38d4e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -327,16 +327,13 @@ void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { float length = endTime - startTime; - if (length <= 0.0f) { - return startTime; - } - while (t < startTime) { - t += length; + if (t < startTime) { + return endTime - ((startTime - t) - static_cast(static_cast((startTime - t) / length)) * length); } - while (t > endTime) { - t -= length; + if (t < endTime) { + return t; } - return t; + return startTime + ((t - endTime) - static_cast(static_cast((t - endTime) / length)) * length); } int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 6c0ca62e4..39d790ffc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -159,16 +159,13 @@ float FnTurnBlender::GetFrequency() const { float FnTurnBlender::CycleTime(float t, float startTime, float endTime) const { float length = endTime - startTime; - if (length <= 0.0f) { - return startTime; - } - while (t < startTime) { - t += length; + if (t < startTime) { + return endTime - ((startTime - t) - static_cast(static_cast((startTime - t) / length)) * length); } - while (t > endTime) { - t -= length; + if (t < endTime) { + return t; } - return t; + return startTime + ((t - endTime) - static_cast(static_cast((t - endTime) / length)) * length); } int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { From 8c82c253126fd009b8fbfcdb89f13ffe09047cd1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:31:35 +0100 Subject: [PATCH 101/372] 73.9%: recover align and raw state helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 38 +++++++++++++--- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 44 ++++++++++++++++--- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 27 ++++++++---- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 95ca38d4e..00198217a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -361,12 +361,38 @@ void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const } void FnRunBlender::AlignCycleBeginEnd(int cIdx) { - mCycleIdx = cIdx; - mInit = true; - mAlignQ.x = 0.0f; - mAlignQ.y = 0.0f; - mAlignQ.z = 0.0f; - mAlignQ.w = 1.0f; + if (!mInit) { + mInit = true; + mCycleIdx = -1; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; + } else if (mCycleIdx != cIdx) { + float beginFacing[2]; + float endFacing[2]; + UMath::Vector4 q; + PhaseChan *phase0 = const_cast(mPhases[mIdx]); + PhaseChan *phase1 = const_cast(mPhases[mIdx + 1]); + + BlendFacing(0.0f, 0.0f, beginFacing); + BlendFacing(static_cast(phase0->mNumFrames - 1), static_cast(phase1->mNumFrames - 1), endFacing); + ComputeAlignQ(beginFacing, endFacing, q); + if (mCycleIdx - 1 == cIdx) { + q.y = -q.y; + } + + float x = mAlignQ.x; + float y = mAlignQ.y; + float z = mAlignQ.z; + float w = mAlignQ.w; + + mAlignQ.x = w * q.x + z * q.y + (x * q.w - y * q.z); + mAlignQ.w = w * q.w + ((-x * q.x - y * q.y) - z * q.z); + mAlignQ.y = w * q.y + ((x * q.z + y * q.w) - z * q.x); + mAlignQ.z = w * q.z + z * q.w - x * q.y + y * q.x; + mCycleIdx = cIdx; + } } void FnRunBlender::AlignRootQ(float *sqt) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 39d790ffc..feb1af77e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -11,6 +11,8 @@ float turnLength(float *v) { return sqrtf(v[0] * v[0] + v[1] * v[1]); } +static int i_6840; + namespace EAGL4Anim { @@ -193,12 +195,42 @@ void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const } void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { - mCycleIdx = cIdx; - mInit = true; - mAlignQ.x = 0.0f; - mAlignQ.y = 0.0f; - mAlignQ.z = 0.0f; - mAlignQ.w = 1.0f; + if (!mInit) { + mInit = true; + mCycleIdx = -1; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; + } else if (mCycleIdx != cIdx) { + float beginFacing[2]; + float endFacing[2]; + UMath::Vector4 q; + + BlendBeginFacing(beginFacing); + BlendEndFacing(endFacing); + ComputeAlignQ(beginFacing, endFacing, q); + if (mCycleIdx - 1 == cIdx) { + q.y = -q.y; + } + + float x = mAlignQ.x; + float y = mAlignQ.y; + float z = mAlignQ.z; + float w = mAlignQ.w; + float newX = (x * q.w - y * q.z) + z * q.y + w * q.x; + float newY = ((x * q.z + y * q.w) - z * q.x) + w * q.y; + float newZ = -x * q.y + y * q.x + z * q.w + w * q.z; + float newW = ((-x * q.x - y * q.y) - z * q.z) + w * q.w; + + i_6840++; + mAlignQ.x = newX; + mAlignQ.y = newY; + mAlignQ.z = newZ; + mAlignQ.w = newW; + mCycleIdx = cIdx; + printf("turn align[%d] Q: %g %g %g %g\n\n", i_6840 - 1, mAlignQ.x, mAlignQ.y, mAlignQ.z, mAlignQ.w); + } } void FnTurnBlender::AlignRootQ(float *sqt) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 9cab8cd43..df959e5c8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -102,18 +102,25 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { bool FnRawStateChan::EvalState(float time, State *s) { RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + unsigned char numFields = rawStateChan->GetNumFields(); unsigned char keySize = rawStateChan->GetKeySize(); - unsigned char *keyData = GetRawStateKeyData(rawStateChan); + unsigned char *keyData; int keyIdx = mKeyIdx; - if (*reinterpret_cast(&keyData[keyIdx * keySize]) > time) { + if ((numFields & 1) == 0) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; + } else { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + } + + if (time < *reinterpret_cast(&keyData[keyIdx * keySize])) { keyIdx--; - while (keyIdx >= 0) { - float *keyTime = reinterpret_cast(&keyData[keyIdx * keySize]); + while (keyIdx > -1) { + unsigned char *currKey = &keyData[keyIdx * keySize]; - if (*keyTime <= time) { - Decode(reinterpret_cast(keyTime) + sizeof(float), reinterpret_cast(s)); + if (*reinterpret_cast(currKey) <= time) { + Decode(currKey + sizeof(float), reinterpret_cast(s)); mKeyIdx = keyIdx; return true; } @@ -124,7 +131,9 @@ bool FnRawStateChan::EvalState(float time, State *s) { Decode(keyData + sizeof(float), reinterpret_cast(s)); mKeyIdx = 0; } else { - for (; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { + unsigned short numKeys = rawStateChan->GetNumKeys(); + + for (; keyIdx < numKeys; keyIdx++) { unsigned char *currKey = &keyData[keyIdx * keySize]; if (time < *reinterpret_cast(currKey + keySize)) { @@ -134,8 +143,8 @@ bool FnRawStateChan::EvalState(float time, State *s) { } } - Decode(keyData + keySize * (rawStateChan->GetNumKeys() - 1) + sizeof(float), reinterpret_cast(s)); - mKeyIdx = rawStateChan->GetNumKeys() - 1; + Decode(keyData + keySize * (numKeys - 1) + sizeof(float), reinterpret_cast(s)); + mKeyIdx = numKeys - 1; } return true; From f43fd586640ba4e53adb7bc3ee5a4ce770175e1c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:32:55 +0100 Subject: [PATCH 102/372] 74.1%: recover run blender velocity path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 00198217a..53026a1d3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -211,28 +211,31 @@ bool FnRunBlender::EvalPhase(float currTime, PhaseValue &phase) { } bool FnRunBlender::EvalVel2D(float currTime, float *vel) { - if (!mFnAnims[0]) { + mPrevTime = currTime; + if (!mVels) { + return false; + } + if (!mFnVelAnims[0]) { SetWeight(0.0f); } float evalTime = currTime + mOffset; - float t0 = CycleTime(mFreq * mCycles[0] * evalTime + mAlignFrame[0], 0.0f, mCycles[0]); - float t1 = CycleTime(mFreq * mCycles[1] * evalTime + mAlignFrame[1], 0.0f, mCycles[1]); - - if (mWeight != 0.0f && mFnVelAnims[0] && mFnVelAnims[1]) { - if (!BlendVel(t0, t1, vel)) { - return false; - } - } else if (mFnVelAnims[0]) { - if (!mFnVelAnims[0]->EvalVel2D(t0, vel)) { - return false; - } - } else { - return false; + float cycleTime0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; + int cycleIdx = ComputeCycleIdx(cycleTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + float t0 = CycleTime(cycleTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + float t1 = CycleTime( + mFreq * mCycles[1] * evalTime + mAlignFrame[1], + 0.0f, + static_cast(mPhases[mIdx + 1]->mNumFrames - 1) + ); + + if (BlendVel(t0, t1, vel)) { + AlignCycleBeginEnd(cycleIdx); + AlignVel(vel); + return true; } - AlignVel(vel); - return true; + return false; } bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { From 5a4bcbe6d817624476e1e12a6100be7ebedd274b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:34:10 +0100 Subject: [PATCH 103/372] 74.3%: recover turn blender velocity path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index feb1af77e..99e704e7d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -114,28 +114,29 @@ bool FnTurnBlender::EvalPhase(float currTime, PhaseValue &phase) { } bool FnTurnBlender::EvalVel2D(float currTime, float *vel) { + mPrevTime = currTime; if (!mFnAnims[0]) { SetWeight(0.0f); } float evalTime = currTime + mOffset; - float t0 = CycleTime(mFreq * mCycles[0] * evalTime + mOffsets[0], 0.0f, mCycles[0]); - float t1 = CycleTime(mFreq * mCycles[1] * evalTime + mOffsets[1], 0.0f, mCycles[1]); + int cycleIdx = ComputeCycleIdx(evalTime, 0.0f, 2.0f / mFreq); - if (mWeight != 0.0f && mFnAnims[0] && mFnAnims[1]) { - if (!BlendVel(t0, t1, vel)) { - return false; - } - } else if (mFnAnims[0]) { - if (!mFnAnims[0]->EvalVel2D(t0, vel)) { - return false; - } - } else { - return false; + printf("currTime: %g offset: %g cycle: %g\n", evalTime, mOffset, 1.0f / mFreq); + printf("offset0: %g offset1: %g\n", mOffsets[0], mOffsets[1]); + printf("cycle0: %g cycle1: %g\n", mCycles[0], mCycles[1]); + + float t0 = mFreq * mCycles[0] * evalTime; + float t1 = mFreq * mCycles[1] * evalTime; + + printf("before offset t0: %g t1: %g\n", t0, t1); + if (BlendVel(CycleTime(t0, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0], CycleTime(t1, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1], vel)) { + AlignCycleBeginEnd(cycleIdx); + AlignVel(vel); + return true; } - AlignVel(vel); - return true; + return false; } bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { From 58df38b15896c809c6ac9e9b47c29070293ec719 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:40:33 +0100 Subject: [PATCH 104/372] 74.5%: recover blender sqt alignment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 12 +++++++++--- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 53026a1d3..358ba3fa2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -76,9 +76,14 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon SetWeight(0.0f); } + const PhaseChan *phase0 = mPhases[mIdx]; + const PhaseChan *phase1 = mPhases[mIdx + 1]; float evalTime = currTime + mOffset; - float t0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; - float t1 = mFreq * mCycles[1] * evalTime + mAlignFrame[1]; + float evalTime0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; + float evalTime1 = mFreq * mCycles[1] * evalTime + mAlignFrame[1]; + int cycleIdx = ComputeCycleIdx(evalTime0, 0.0f, static_cast(phase0->mNumFrames - 1)); + float t0 = CycleTime(evalTime0, 0.0f, static_cast(phase0->mNumFrames - 1)); + float t1 = CycleTime(evalTime1, 0.0f, static_cast(phase1->mNumFrames - 1)); mSkeleton->GetStillPose(sqtBuffer, 0); if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { @@ -103,6 +108,8 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, boneMask); } + AlignCycleBeginEnd(cycleIdx); + AlignRootQ(sqtBuffer); return true; } @@ -389,7 +396,6 @@ void FnRunBlender::AlignCycleBeginEnd(int cIdx) { float y = mAlignQ.y; float z = mAlignQ.z; float w = mAlignQ.w; - mAlignQ.x = w * q.x + z * q.y + (x * q.w - y * q.z); mAlignQ.w = w * q.w + ((-x * q.x - y * q.y) - z * q.z); mAlignQ.y = w * q.y + ((x * q.z + y * q.w) - z * q.x); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 99e704e7d..f1670404d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -50,8 +50,9 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo } float evalTime = currTime + mOffset; - float t0 = CycleTime(mFreq * mCycles[0] * evalTime + mOffsets[0], 0.0f, mCycles[0]); - float t1 = CycleTime(mFreq * mCycles[1] * evalTime + mOffsets[1], 0.0f, mCycles[1]); + int cycleIdx = ComputeCycleIdx(evalTime, 0.0f, 2.0f / mFreq); + float t0 = CycleTime(mFreq * mCycles[0] * evalTime, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; + float t1 = CycleTime(mFreq * mCycles[1] * evalTime, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; mSkeleton->GetStillPose(sqtBuffer, 0); if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { @@ -59,7 +60,7 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo } if (mWeight != 0.0f && mFnAnims[1]) { - ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); + ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(1); unsigned int bufferSize = static_cast(mSkeleton->GetNumBones() * 12 * sizeof(float)); if (scratch.GetSize() < bufferSize) { @@ -76,6 +77,7 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, boneMask); } + AlignCycleBeginEnd(cycleIdx); AlignRootQ(sqtBuffer); return true; } From 90bcac7a1691d82603a2825a3478e99b4a0fbdd0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:43:42 +0100 Subject: [PATCH 105/372] 74.6%: tighten blender sqt scratch ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 17 ++++------------- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 9 +-------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 358ba3fa2..3c69056cb 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -76,14 +76,12 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon SetWeight(0.0f); } - const PhaseChan *phase0 = mPhases[mIdx]; - const PhaseChan *phase1 = mPhases[mIdx + 1]; float evalTime = currTime + mOffset; float evalTime0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; float evalTime1 = mFreq * mCycles[1] * evalTime + mAlignFrame[1]; - int cycleIdx = ComputeCycleIdx(evalTime0, 0.0f, static_cast(phase0->mNumFrames - 1)); - float t0 = CycleTime(evalTime0, 0.0f, static_cast(phase0->mNumFrames - 1)); - float t1 = CycleTime(evalTime1, 0.0f, static_cast(phase1->mNumFrames - 1)); + int cycleIdx = ComputeCycleIdx(evalTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + float t0 = CycleTime(evalTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + float t1 = CycleTime(evalTime1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); mSkeleton->GetStillPose(sqtBuffer, 0); if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { @@ -91,14 +89,7 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon } if (mWeight != 0.0f && mFnAnims[1]) { - ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); - unsigned int bufferSize = static_cast(mSkeleton->GetNumBones() * 12 * sizeof(float)); - - if (scratch.GetSize() < bufferSize) { - scratch.AllocateBuffer(bufferSize); - } - - float *blendPose = reinterpret_cast(scratch.GetBuffer()); + float *blendPose = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); mSkeleton->GetStillPose(blendPose, 0); if (!mFnAnims[1]->EvalSQT(t1, blendPose, 0)) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index f1670404d..524351fbe 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -60,14 +60,7 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo } if (mWeight != 0.0f && mFnAnims[1]) { - ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(1); - unsigned int bufferSize = static_cast(mSkeleton->GetNumBones() * 12 * sizeof(float)); - - if (scratch.GetSize() < bufferSize) { - scratch.AllocateBuffer(bufferSize); - } - - float *blendPose = reinterpret_cast(scratch.GetBuffer()); + float *blendPose = reinterpret_cast(ScratchBuffer::GetScratchBuffer(1).GetBuffer()); mSkeleton->GetStillPose(blendPose, 0); if (!mFnAnims[1]->EvalSQT(t1, blendPose, 0)) { From 2b07c4346c640bd0b13486c14157151ea82b93c9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:45:51 +0100 Subject: [PATCH 106/372] 74.7%: tighten blender sqt blend paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 8 ++++---- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 3c69056cb..11ae6b59e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -76,9 +76,9 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon SetWeight(0.0f); } - float evalTime = currTime + mOffset; - float evalTime0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; - float evalTime1 = mFreq * mCycles[1] * evalTime + mAlignFrame[1]; + currTime += mOffset; + float evalTime0 = mFreq * mCycles[0] * currTime + mAlignFrame[0]; + float evalTime1 = mFreq * mCycles[1] * currTime + mAlignFrame[1]; int cycleIdx = ComputeCycleIdx(evalTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); float t0 = CycleTime(evalTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); float t1 = CycleTime(evalTime1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); @@ -96,7 +96,7 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon return false; } - FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, boneMask); + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, nullptr); } AlignCycleBeginEnd(cycleIdx); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 524351fbe..834249fb1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -49,10 +49,10 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo SetWeight(0.0f); } - float evalTime = currTime + mOffset; - int cycleIdx = ComputeCycleIdx(evalTime, 0.0f, 2.0f / mFreq); - float t0 = CycleTime(mFreq * mCycles[0] * evalTime, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; - float t1 = CycleTime(mFreq * mCycles[1] * evalTime, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; + currTime += mOffset; + int cycleIdx = ComputeCycleIdx(currTime, 0.0f, 2.0f / mFreq); + float t0 = CycleTime(mFreq * mCycles[0] * currTime, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; + float t1 = CycleTime(mFreq * mCycles[1] * currTime, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; mSkeleton->GetStillPose(sqtBuffer, 0); if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { @@ -67,7 +67,7 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo return false; } - FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, boneMask); + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, nullptr); } AlignCycleBeginEnd(cycleIdx); From 7b088051bff81509e2ac498fafe4821c9606d07d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:48:26 +0100 Subject: [PATCH 107/372] 74.8%: restore blender cycle index branches Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 10 ++++++---- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 11ae6b59e..a1d878fc2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -340,11 +340,13 @@ float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { float length = endTime - startTime; - if (length <= 0.0f) { - return 0; + if (t < startTime) { + return static_cast((startTime - t) / length); } - - return static_cast((t - startTime) / length); + if (endTime <= t) { + return static_cast((t - endTime) / length) + 1; + } + return 0; } void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 834249fb1..0fe861195 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -169,11 +169,13 @@ float FnTurnBlender::CycleTime(float t, float startTime, float endTime) const { int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { float length = endTime - startTime; - if (length <= 0.0f) { - return 0; + if (t < startTime) { + return static_cast((startTime - t) / length); } - - return static_cast((t - startTime) / length); + if (endTime <= t) { + return static_cast((t - endTime) / length) + 1; + } + return 0; } void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { From 4c0e56f9f4751fa65e69d843f7c5cb9e9b8b7e6a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:51:13 +0100 Subject: [PATCH 108/372] 75.0%: recover turn blender weight flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 0fe861195..85e4bbd72 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -81,6 +81,7 @@ void FnTurnBlender::Eval(float prevTime, float currTime, float *pose) { void FnTurnBlender::SetWeight(float w) { int idx = static_cast(w); + int oldIdx = mIdx; if (idx < 0) { idx = 0; @@ -90,17 +91,35 @@ void FnTurnBlender::SetWeight(float w) { } mWeight = w - static_cast(idx); + if (idx == oldIdx) { + float prevFreq = mFreq; + + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; + } else { + if (idx == oldIdx + 1) { + mFnAnims[0] = mFnAnims[1]; + mFnAnims[1] = mAnims[idx + 1]; + } else if (idx == oldIdx - 1) { + mFnAnims[1] = mFnAnims[0]; + mFnAnims[0] = mAnims[idx]; + } else { + mFnAnims[0] = mAnims[idx]; + mFnAnims[1] = mAnims[idx + 1]; + } - if (idx >= 0 && idx < mNumAnims) { - mFnAnims[0] = mAnims[idx]; - mFnAnims[1] = mAnims[idx + 1]; mIdx = idx; - mCycles[0] = 1.0f; - mCycles[1] = 1.0f; - mOffsets[0] = 0.0f; - mOffsets[1] = 0.0f; - mFreq = 1.0f; - mOffset = 0.0f; + + FnRunBlender *anim0 = static_cast(mAnims[idx]); + FnRunBlender *anim1 = static_cast(mAnims[mIdx + 1]); + float prevFreq = mFreq; + + mCycles[0] = 1.0f / anim0->GetFrequency(); + mOffsets[0] = anim0->GetOffset(); + mCycles[1] = 1.0f / anim1->GetFrequency(); + mOffsets[1] = anim1->GetOffset(); + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; } } From 95c11e1b790c4cef67513e9219c59e913d26f9ae Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 09:59:46 +0100 Subject: [PATCH 109/372] 75.0%: tighten blender phase and cycle helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 14 +------------- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index a1d878fc2..1df828658 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -193,18 +193,6 @@ void FnRunBlender::SetWeight(float w) { } bool FnRunBlender::EvalPhase(float currTime, PhaseValue &phase) { - if (!mFnAnims[0]) { - SetWeight(0.0f); - } - - if (mPhases && mIdx >= 0 && mIdx < mNumAnims) { - FnPhaseChan phaseChan; - - phaseChan.SetAnimMemoryMap(const_cast(mPhases[mIdx])); - phaseChan.Eval(0.0f, currTime, &phase.mAngle); - return true; - } - return false; } @@ -343,7 +331,7 @@ int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const if (t < startTime) { return static_cast((startTime - t) / length); } - if (endTime <= t) { + if (t >= endTime) { return static_cast((t - endTime) / length) + 1; } return 0; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 85e4bbd72..bdc016b5f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -191,7 +191,7 @@ int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) cons if (t < startTime) { return static_cast((startTime - t) / length); } - if (endTime <= t) { + if (t >= endTime) { return static_cast((t - endTime) / length) + 1; } return 0; From 7c6425b76c920e53ca96eb96755eaa7879f2e1d7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:02:19 +0100 Subject: [PATCH 110/372] 75.1%: restore blender deleting destructors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h | 5 ++++- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 9 ++++++++- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h index 199b82da3..d05e7a018 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h @@ -8,6 +8,7 @@ #include "AnimMemoryMap.h" #include "FnAnim.h" #include "Skeleton.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -18,7 +19,9 @@ class FnRunBlender : public FnAnim { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index bdc016b5f..3ea040fdc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -40,7 +40,14 @@ FnTurnBlender::FnTurnBlender() mAlignQ.w = 1.0f; } -FnTurnBlender::~FnTurnBlender() {} +FnTurnBlender::~FnTurnBlender() { + if (mNumAnims) { + ScratchBuffer::GetScratchBuffer(1).FreeBuffer(); + } + if (mAnims) { + MemoryPoolManager::DeleteBlock(mAnims); + } +} bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *boneMask) { mPrevTime = currTime; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h index 0f23574f0..7bbab190e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h @@ -7,6 +7,7 @@ #include "FnAnim.h" #include "Skeleton.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class FnTurnBlender : public FnAnim { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} From a19fd6503a66679afe96baa511a8c6d6a2eb839a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:10:00 +0100 Subject: [PATCH 111/372] 75.4%: restore blender velocity normalization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 28 +++++++++++++++++-- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 28 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 1df828658..794799c46 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -231,12 +231,34 @@ bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { if (!mFnVelAnims[0] || !mFnVelAnims[1]) { return false; } - if (!mFnVelAnims[0]->EvalVel2D(t0, vel0) || !mFnVelAnims[1]->EvalVel2D(t1, vel1)) { + if (!mFnVelAnims[0]->EvalVel2D(t0, vel0)) { return false; } + if (mWeight != 0.0f) { + if (!mFnVelAnims[1]->EvalVel2D(t1, vel1)) { + return false; + } + + float weight1 = 1.0f - mWeight; + + vel[0] = mWeight * vel1[0] + weight1 * vel0[0]; + vel[1] = mWeight * vel1[1] + weight1 * vel0[1]; + + float velLength = length(vel); + + if (velLength != 0.0f) { + float vel1Length = length(vel1); + float vel0Length = length(vel0); + float scale = (mWeight * vel1Length + weight1 * vel0Length) / velLength; + + vel[1] = vel[1] * scale; + vel[0] = vel[0] * scale; + } + } else { + vel[1] = vel0[1]; + vel[0] = vel0[0]; + } - vel[0] = vel0[0] + mWeight * (vel1[0] - vel0[0]); - vel[1] = vel0[1] + mWeight * (vel1[1] - vel0[1]); return true; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 3ea040fdc..83a890cf3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -167,12 +167,34 @@ bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { if (!mFnAnims[0] || !mFnAnims[1]) { return false; } - if (!mFnAnims[0]->EvalVel2D(t0, vel0) || !mFnAnims[1]->EvalVel2D(t1, vel1)) { + if (!mFnAnims[0]->EvalVel2D(t0, vel0)) { return false; } + if (mWeight != 0.0f) { + if (!mFnAnims[1]->EvalVel2D(t1, vel1)) { + return false; + } + + float weight1 = 1.0f - mWeight; + + vel[0] = mWeight * vel1[0] + weight1 * vel0[0]; + vel[1] = mWeight * vel1[1] + weight1 * vel0[1]; + + float velLength = length(vel); + + if (velLength != 0.0f) { + float vel1Length = length(vel1); + float vel0Length = length(vel0); + float scale = (mWeight * vel1Length + weight1 * vel0Length) / velLength; + + vel[1] = vel[1] * scale; + vel[0] = vel[0] * scale; + } + } else { + vel[1] = vel0[1]; + vel[0] = vel0[0]; + } - vel[0] = vel0[0] + mWeight * (vel1[0] - vel0[0]); - vel[1] = vel0[1] + mWeight * (vel1[1] - vel0[1]); return true; } From fe79268c9da56b59156e65927f91d8489729f807 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:17:39 +0100 Subject: [PATCH 112/372] 75.4%: expand turn facing extraction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 83a890cf3..184693bbc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -325,8 +325,25 @@ bool FnTurnBlender::BlendBeginFacing(float *f) const { reinterpret_cast(mFnAnims[1])->ComputeBeginRootQ(q1); FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); - f[0] = 2.0f * (q.w * q.z - q.x * q.y); - f[1] = 2.0f * (q.y * q.z + q.x * q.w); + UMath::Vector4 oldF; + oldF.x = 0.0f; + oldF.y = 1.0f; + oldF.z = 0.0f; + oldF.w = 1.0f; + + float doubleY = q.y + q.y; + float doubleZ = q.z + q.z; + float xx2 = q.x * (q.x + q.x); + float wx2 = q.w * (q.x + q.x); + float newY = oldF.x * (q.x * doubleZ - q.w * doubleY) + oldF.y * (q.y * doubleZ + wx2) + + oldF.z * (oldF.w - (xx2 + q.y * doubleY)); + float newX = oldF.x * (oldF.w - (q.y * doubleY + q.z * doubleZ)) + oldF.y * (q.x * doubleY - q.w * doubleZ) + + oldF.z * (q.x * doubleZ + q.w * doubleY); + float newZ = oldF.x * (q.x * doubleY + q.w * doubleZ) + oldF.y * (oldF.w - (xx2 + q.z * doubleZ)) + + oldF.z * (q.y * doubleZ - wx2); + + f[1] = newY; + f[0] = newX; printf("Facing: %g %g\n", f[0], f[1]); return true; } @@ -344,8 +361,25 @@ bool FnTurnBlender::BlendEndFacing(float *f) const { reinterpret_cast(mFnAnims[1])->ComputeEndRootQ(q1); FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); - f[0] = 2.0f * (q.w * q.z - q.x * q.y); - f[1] = 2.0f * (q.y * q.z + q.x * q.w); + UMath::Vector4 oldF; + oldF.x = 0.0f; + oldF.y = 1.0f; + oldF.z = 0.0f; + oldF.w = 1.0f; + + float doubleY = q.y + q.y; + float doubleZ = q.z + q.z; + float xx2 = q.x * (q.x + q.x); + float wx2 = q.w * (q.x + q.x); + float newY = oldF.x * (q.x * doubleZ - q.w * doubleY) + oldF.y * (q.y * doubleZ + wx2) + + oldF.z * (oldF.w - (xx2 + q.y * doubleY)); + float newX = oldF.x * (oldF.w - (q.y * doubleY + q.z * doubleZ)) + oldF.y * (q.x * doubleY - q.w * doubleZ) + + oldF.z * (q.x * doubleZ + q.w * doubleY); + float newZ = oldF.x * (q.x * doubleY + q.w * doubleZ) + oldF.y * (oldF.w - (xx2 + q.z * doubleZ)) + + oldF.z * (q.y * doubleZ - wx2); + + f[1] = newY; + f[0] = newX; printf("Facing: %g %g\n", f[0], f[1]); return true; } From 7ca2eb05d24984e0a84239eeef893ae6bc6f1a52 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:22:30 +0100 Subject: [PATCH 113/372] 75.4%: reshuffle deltaq init ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index bd093e340..c619fa647 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -163,17 +163,19 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (!mBins) { unsigned char numBones = deltaQ->mNumBones; + DeltaQMinRange *minRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); - mMinRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); - mBins = reinterpret_cast(mMinRanges) + numBones * sizeof(DeltaQMinRange); + mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); if (numBones != 0) { - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; + UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + mPrevQs = prevQs; + mPrevQBlock = prevQs; } + mMinRanges = minRanges; } int floorTime = FloatToInt(currTime); int floorKey; @@ -351,17 +353,19 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (!mBins) { unsigned char numBones = deltaQ->mNumBones; + DeltaQMinRange *minRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); - mMinRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); - mBins = reinterpret_cast(mMinRanges) + numBones * sizeof(DeltaQMinRange); + mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); if (numBones != 0) { - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; + UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + mPrevQs = prevQs; + mPrevQBlock = prevQs; } + mMinRanges = minRanges; } int floorTime = FloatToInt(currTime); int floorKey; From 811809c2df059470523e25dbf96e3b8ad3bce40c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:24:34 +0100 Subject: [PATCH 114/372] 75.5%: retime singleq min-range cache Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 0810e3d88..8023e6292 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -199,7 +199,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); if (!mPrevQs) { - mMinRanges = GetSingleQMinRanges(deltaQ); + DeltaSingleQMinRange *minRanges = GetSingleQMinRanges(deltaQ); mBins = GetSingleQBinStart(deltaQ); mBinSize = GetSingleQBinSize(deltaQ); @@ -208,6 +208,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); mPostMultQs = @@ -248,6 +249,8 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); } } + } else { + mMinRanges = minRanges; } } int floorTime = FloatToInt(currTime); @@ -402,7 +405,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); if (!mPrevQs) { - mMinRanges = GetSingleQMinRanges(deltaQ); + DeltaSingleQMinRange *minRanges = GetSingleQMinRanges(deltaQ); mBins = GetSingleQBinStart(deltaQ); mBinSize = GetSingleQBinSize(deltaQ); @@ -411,6 +414,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); mPostMultQs = @@ -451,6 +455,8 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); } } + } else { + mMinRanges = minRanges; } } int floorTime = FloatToInt(currTime); From e9445ffa8064cc53df39c30f2cd7bf43a6513d95 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:31:51 +0100 Subject: [PATCH 115/372] 75.7%: recover delta const-array helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h | 4 +- src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h | 4 +- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 36 +++++++++++------ .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 40 ++++++++++++------- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h index caca67ba4..b0c56d7ef 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h @@ -108,9 +108,9 @@ struct DeltaQ : public AnimMemoryMap { DeltaQDelta *GetDelta(unsigned char *binData, int deltaIdx) {} - unsigned short *GetConstBoneIdx(); + unsigned char *GetConstBoneIdx(); - float *GetConstPhysical(); + DeltaQPhysical *GetConstPhysical(); unsigned short mNumKeys; // offset 0x4, size 0x2 unsigned char mNumBones; // offset 0x6, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h index e743e6ce4..042369d62 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h @@ -108,9 +108,9 @@ struct DeltaQFast : public AnimMemoryMap { return reinterpret_cast(&binData[mNumBones * 6 + deltaIdx * mNumBones * 3]); } - unsigned short *GetConstBoneIdx(); + unsigned char *GetConstBoneIdx(); - void *GetConstPhysical(); + DeltaQFastPhysical *GetConstPhysical(); unsigned short mNumKeys; // offset 0x4, size 0x2 unsigned char mNumBones; // offset 0x6, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index c619fa647..6c9572a88 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -6,22 +6,34 @@ namespace EAGL4Anim { -unsigned short *DeltaQ::GetConstBoneIdx() { - const int binSize = GetBinSize(); - int numBins = mNumKeys >> GetBinLengthPower(); - unsigned char *s = &GetBin(0)[binSize * numBins]; - int r = mNumKeys & GetBinLengthModMask(); - - if (r > 0) { - s = reinterpret_cast( - AlignSize2(reinterpret_cast(s + mNumBones * sizeof(DeltaQPhysical) + ((r - 1) * mNumBones * sizeof(DeltaQDelta))))); +unsigned char *DeltaQ::GetConstBoneIdx() { + unsigned int numBones = mNumBones; + unsigned int binLength = 1u << mBinLengthPower; + unsigned int numBins = mNumKeys / binLength; + unsigned int remainder = mNumKeys - numBins * binLength; + int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQMinRange) + + AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + + if (remainder > 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); } - return reinterpret_cast(s); + return reinterpret_cast(s); } -float *DeltaQ::GetConstPhysical() { - return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); +DeltaQPhysical *DeltaQ::GetConstPhysical() { + unsigned int numBones = mNumBones; + unsigned int binLength = 1u << mBinLengthPower; + unsigned int numBins = mNumKeys / binLength; + unsigned int remainder = mNumKeys - numBins * binLength; + int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQMinRange) + + AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + + if (remainder > 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); + } + + return reinterpret_cast(AlignSize2(s + mNumConstBones)); } namespace { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index e4c6a3947..94d12dab3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -32,22 +32,34 @@ void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector namespace EAGL4Anim { -unsigned short *DeltaQFast::GetConstBoneIdx() { - const int binSize = GetBinSize(); - int numFrames = GetNumFrames(); - int numBins = numFrames >> GetBinLengthPower(); - unsigned char *s = &GetBin(0)[binSize * numBins]; - int r = numFrames & GetBinLengthModMask(); +unsigned char *DeltaQFast::GetConstBoneIdx() { + unsigned int numBones = mNumBones; + unsigned int binLength = 1u << mBinLengthPower; + unsigned int numBins = mNumKeys / binLength; + unsigned int remainder = mNumKeys - numBins * binLength; + int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQFastMinRange) + + AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; - if (r > 0) { - s = reinterpret_cast(AlignSize2(reinterpret_cast(s + mNumBones * 6 + ((r - 1) * mNumBones * 3)))); + if (remainder > 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); } - return reinterpret_cast(s); + return reinterpret_cast(s); } -void *DeltaQFast::GetConstPhysical() { - return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); +DeltaQFastPhysical *DeltaQFast::GetConstPhysical() { + unsigned int numBones = mNumBones; + unsigned int binLength = 1u << mBinLengthPower; + unsigned int numBins = mNumKeys / binLength; + unsigned int remainder = mNumKeys - numBins * binLength; + int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQFastMinRange) + + AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; + + if (remainder > 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); + } + + return reinterpret_cast(AlignSize2(s + mNumConstBones)); } namespace { @@ -497,10 +509,10 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) floorDeltaIdx = floorKey & binLengthMask; if (mNextKey == floorKey) { - UMath::Vector4 *swapQs = mPrevQs; + UMath::Vector4 *swapQs = mNextQs; - mPrevQs = mNextQs; - mNextQs = swapQs; + mNextQs = mPrevQs; + mPrevQs = swapQs; mNextKey = prevKey; } else { int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; From b115367340f87522c3efed04e0c153c5c31eccb5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 10:33:30 +0100 Subject: [PATCH 116/372] 75.8%: split delta const base offsets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 17 +++++++++++------ src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 17 +++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 6c9572a88..2e4eb32df 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -11,13 +11,16 @@ unsigned char *DeltaQ::GetConstBoneIdx() { unsigned int binLength = 1u << mBinLengthPower; unsigned int numBins = mNumKeys / binLength; unsigned int remainder = mNumKeys - numBins * binLength; - int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQMinRange) + - AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + int s = reinterpret_cast(this) + 0x12; - if (remainder > 0) { - s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); + s += numBones * sizeof(DeltaQMinRange); + s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + + if (!remainder) { + return reinterpret_cast(s); } + s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); return reinterpret_cast(s); } @@ -26,8 +29,10 @@ DeltaQPhysical *DeltaQ::GetConstPhysical() { unsigned int binLength = 1u << mBinLengthPower; unsigned int numBins = mNumKeys / binLength; unsigned int remainder = mNumKeys - numBins * binLength; - int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQMinRange) + - AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + int s = reinterpret_cast(this) + 0x12; + + s += numBones * sizeof(DeltaQMinRange); + s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; if (remainder > 0) { s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 94d12dab3..747505e3f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -37,13 +37,16 @@ unsigned char *DeltaQFast::GetConstBoneIdx() { unsigned int binLength = 1u << mBinLengthPower; unsigned int numBins = mNumKeys / binLength; unsigned int remainder = mNumKeys - numBins * binLength; - int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQFastMinRange) + - AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; + int s = reinterpret_cast(this) + 0x12; - if (remainder > 0) { - s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); + s += numBones * sizeof(DeltaQFastMinRange); + s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; + + if (!remainder) { + return reinterpret_cast(s); } + s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); return reinterpret_cast(s); } @@ -52,8 +55,10 @@ DeltaQFastPhysical *DeltaQFast::GetConstPhysical() { unsigned int binLength = 1u << mBinLengthPower; unsigned int numBins = mNumKeys / binLength; unsigned int remainder = mNumKeys - numBins * binLength; - int s = reinterpret_cast(this) + 0x12 + numBones * sizeof(DeltaQFastMinRange) + - AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; + int s = reinterpret_cast(this) + 0x12; + + s += numBones * sizeof(DeltaQFastMinRange); + s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; if (remainder > 0) { s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); From 1aebf4e219b77aea23e2f96ecc412a51321f0418 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 11:29:40 +0100 Subject: [PATCH 117/372] 76.3%: unify qfast eval slerp path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 101 ++++++------------ 1 file changed, 33 insertions(+), 68 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 747505e3f..4a82575d1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -303,7 +303,7 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * kQFastPhysicalScale12 - - kQFastPhysicalBias12; + kQFastPhysicalBias12; physical += 3; } } else { @@ -601,86 +601,51 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mPrevKey = floorKey; times = deltaQ->mTimes; + bool slerpReqd = false; + float t = 0.0f; if (!times) { float floorTimef = static_cast(floorTime); - if (currTime != floorTimef && static_cast(floorKey) < deltaQ->mNumKeys - 1) { - float t = currTime - floorTimef; - - UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); - - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; - unsigned char boneIdx = boneIdxs[ibone]; - float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; - float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; - float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; - float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); - float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); - - out[0] = x * invNorm; - out[1] = y * invNorm; - out[2] = z * invNorm; - out[3] = w * invNorm; - } - - goto finish_const_bones; + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = currTime - floorTimef; } } else if (floorKey == 0) { - if (currTime != 0.0f && static_cast(floorKey) < deltaQ->mNumKeys - 1) { - float t = currTime / static_cast(times[0]); - - UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); - - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; - unsigned char boneIdx = boneIdxs[ibone]; - float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; - float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; - float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; - float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); - float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); - - out[0] = x * invNorm; - out[1] = y * invNorm; - out[2] = z * invNorm; - out[3] = w * invNorm; - } - - goto finish_const_bones; + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + t = currTime / static_cast(times[0]); } } else { float floorTimef = static_cast(times[floorKey - 1]); - if (currTime != floorTimef && static_cast(floorKey) < deltaQ->mNumKeys - 1) { - float t = (currTime - floorTimef) / (static_cast(times[floorKey]) - floorTimef); + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = (currTime - floorTimef) / (static_cast(times[floorKey]) - floorTimef); + } + } - UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); + if (slerpReqd && static_cast(floorKey) < deltaQ->mNumKeys - 1) { + UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; - unsigned char boneIdx = boneIdxs[ibone]; - float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; - float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; - float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; - float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); - float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); - - out[0] = x * invNorm; - out[1] = y * invNorm; - out[2] = z * invNorm; - out[3] = w * invNorm; - } - - goto finish_const_bones; + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + unsigned char boneIdx = boneIdxs[ibone]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; } + + goto finish_const_bones; } for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { From c1c26ddd0413a5f824907bebb39a6d9968048154 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 11:31:36 +0100 Subject: [PATCH 118/372] 76.4%: gate qfast masked blending Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 4a82575d1..0221b75dd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -720,14 +720,36 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM } int ceilKey = floorKey + 1; + bool slerpReqd = false; + float t = 0.0f; if (ceilKey >= deltaQ->mNumKeys) { ceilKey = floorKey; } - if (ceilKey > floorKey) { + if (!deltaQ->mTimes) { + float floorTimef = static_cast(floorKey); + + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = currTime - floorTimef; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + t = currTime / static_cast(deltaQ->mTimes[0]); + } + } else { + float floorTimef = static_cast(deltaQ->mTimes[floorKey - 1]); + + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = (currTime - floorTimef) / (static_cast(deltaQ->mTimes[floorKey]) - floorTimef); + } + } + + if (slerpReqd && ceilKey > floorKey) { UpdateNextQsMask(deltaQ, ceilKey, floorBinIdx, floorDeltaIdx, boneMask); - float t = ComputeQFastBlendT(deltaQ, floorKey, ceilKey, currTime); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; From 7fbc6f0b4522a2f4e02159be088c3eaec344cd4e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 11:41:37 +0100 Subject: [PATCH 119/372] 76.5%: inline qfast masked quat blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 0221b75dd..8e37edb1a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -758,7 +758,19 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM continue; } - FastQuatBlendF4(t, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&mNextQs[ibone]), &quatBase[boneIdx * 12]); + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float *out = &quatBase[boneIdx * 12]; + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; } } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { From bc6677a844c5d852d662f0105dc38b360a6aaf34 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 11:45:03 +0100 Subject: [PATCH 120/372] 76.7%: flatten qfast masked const decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 8e37edb1a..9563fbd43 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -796,14 +796,15 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM continue; } - UMath::Vector4 q; + unsigned short *physical = reinterpret_cast(&mConstPhysical[ibone]); float *out = &quatBase[boneIdx * 12]; - DecodeQFastPhysical(mConstPhysical[ibone], q); - out[0] = q.x; - out[1] = q.y; - out[2] = q.z; - out[3] = q.w; + out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } return true; From 7cfb7db4d25d6ba4f592052f4b835217b9703da7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 11:48:41 +0100 Subject: [PATCH 121/372] 76.8%: flatten qfast masked floor decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 9563fbd43..25a6ecec8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -706,7 +706,15 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM continue; } - DecodeQFastPhysical(floorPhys[ibone], mPrevQs[ibone]); + unsigned short *physical = reinterpret_cast(&floorPhys[ibone]); + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + + prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } prevDeltaIdx = 0; } else { From 2aaf2f7936d5bd9aee2bea00de2a1b7af8d3e401 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 11:53:52 +0100 Subject: [PATCH 122/372] 76.9%: flatten deltaq const decode path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 2e4eb32df..271e828fd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -355,10 +355,12 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (deltaQ->mNumConstBones != 0) { for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { - UMath::Vector4 constq; + float *out = GetOutputQuat(sqt, mConstBoneIdxs[ibone]); - DecodePhysical(mConstPhysical[ibone], constq); - *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; + out[0] = mConstPhysical[ibone].mX * kRangeScale15Bit - kFloatOne; + out[1] = mConstPhysical[ibone].mY * kRangeScale16Bit - kFloatOne; + out[2] = mConstPhysical[ibone].mZ * kRangeScale16Bit - kFloatOne; + RecoverW(mConstPhysical[ibone].mW, *reinterpret_cast(out)); } } From 07af42d6ecdc054197ff4ed791c93e8e6d1a99a2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 12:13:44 +0100 Subject: [PATCH 123/372] review adjustments --- .github/skills/code_style/SKILL.md | 3 +++ .github/skills/execute/SKILL.md | 7 +++++++ .github/skills/implement/SKILL.md | 4 ++++ .github/skills/scaffold/SKILL.md | 14 +++++++++++++- AGENTS.md | 13 +++++++++++-- 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index d5b2cbbb3..15f4779af 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -122,6 +122,7 @@ Foo::Foo() - Use the repo's header guard form when writing headers: `#ifndef` / `#define` plus the `#ifdef EA_PRAGMA_ONCE_SUPPORTED` / `#pragma once` block. - Keep member layout comments aligned and intact in decomp headers. +- When writing a recovered layout, start from a pasted GC DWARF dump instead of hand-reconstructing a cleaner version. Treat the dump as source-of-truth data entry, then make only small verified fixes from PS2 or existing headers. - Preserve the original `class` / `struct` kind from existing headers or Dwarf / PS2 evidence; do not treat it as a cosmetic style choice. - Treat header declarations as the repo source of truth. If the repo only has local `.cpp` partial declarations, verify the kind with the PS2 dump instead of copying them blindly. - Even forward declarations and local partial declarations should use the accurate keyword when known. @@ -129,6 +130,7 @@ Foo::Foo() - When a recovered type is a `class`, keep explicit access sections and put the method/accessor block before the member layout block unless existing repo evidence shows otherwise. - Preserve the member naming style that DWARF shows. Some types use `mMember`, others use `m_member`; do not normalize them. - Preserve recovered member names, types, order, and offset comments. Do not invent placeholder members named `pad`, `unk`, `unknown`, or `field_XXXX` for game code just to make a layout compile. +- Preserve the dumped declaration order too. Do not regroup methods, helpers, enums, or fields for readability unless an existing repo header or PS2 evidence proves the original order differs. - If a member is genuinely unknown, stop and verify it with `find-symbol.py`, GC Dwarf, and PS2 data. If the layout is still incomplete, add a short TODO above the type instead of burying uncertainty in fake member names. - Add offset / size comments when you are writing recovered type layouts from DWARF. - In recovered layouts, prefer explicit-width aliases such as `uint8` / `uint16` when the field width is known. Use plain `char` for text / byte buffers and `signed char` when the field is a signed 8-bit counter. @@ -200,6 +202,7 @@ Keep the cleanup only if the build succeeds and the relevant match status is unc - Header prologues should keep the `EA_PRAGMA_ONCE_SUPPORTED` block ahead of includes, not after them. - Bare `#if MACRO` presence checks are review bait; use `#ifdef` / `#ifndef` unless you are intentionally testing a numeric config value. - Reviewed recovered headers tend to keep total-size comments above the type, methods before fields, explicit access sections, and fixed-width aliases for width-known narrow integer members. +- Recent `zMisc` review cleanup also showed that hand-reconstructed structs and reordered declarations create avoidable churn; copy recovered layouts from DWARF into the owner header first and keep the dumped order unless PS2/header evidence proves a correction. - Reviewed fixups also remove stale bare recovery markers or replace them with context, and prefer existing list/node helpers over hand-written pointer/link rewiring. - Some reviewed fixups improved readability without losing match by replacing opaque range-check arithmetic with explicit bounds and by moving repeated pointer/boundary math behind short named helpers. - Other recurring review churn came from plain-`int` address helpers, stray local `.cpp` prototypes for shared functions, and integer-coded parser states where named enums were clearer but still matched. diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 5ba0bd156..ea6fef41b 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -91,6 +91,10 @@ definition does not yet exist in the project, follow the scaffold workflow in `.github/skills/scaffold/SKILL.md` to create the needed header/source definitions before moving on. +Treat recovered types here as copied reference data, not as hand-designed headers. Copy +the GC DWARF type body into the canonical owner header first and preserve its declaration +order unless PS2 or existing repo-header evidence proves a specific correction. + ## Phase 3: Implement Functions ### 3a. Get the updated function list @@ -116,6 +120,9 @@ For each missing or nonmatching function, follow the implementation workflow in - **One at a time.** Keep the tree in a coherent state as you work through the list. - **Balance new vs fixing.** Don't get stuck on one stubborn function — sometimes implementing the next function reveals patterns that make the previous one click. +- **Recovered types are not freeform.** If a function forces you to add or fix a type, + copy the DWARF layout into the owner header first. Do not sketch structs/classes from + use sites or reorder declarations just to make the header look nicer. - **Mismatch triage:** - `@stringBase0` offset mismatches often resolve as more string literals are added - If you need to inspect the original string or rodata at a virtual address, use `python tools/elf_lookup.py 0xADDR` diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index cbd94da78..8915ac246 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -90,6 +90,8 @@ Reference the skill for the usage. It gives info based on the virtual address of - If a repo header already exists for the type, include that header instead of introducing a local forward declaration. - Preserve the original `class` vs `struct` kind. If the existing header is missing or incomplete, verify the type kind from GC Dwarf and PS2 info before writing a local declaration. - Preserve real member names and field types too. Do not introduce `pad`, `unk`, or `field_XXXX` members as placeholders for guessed layout; verify the member list from GC Dwarf / PS2 data and leave a TODO when something is still uncertain. +- When a type is missing or incomplete, dump the full class/struct body from GC DWARF and paste that as the starting point. Do not reconstruct the layout from one function's field accesses or from guessed semantics. +- Preserve the dumped declaration order as well as the member order. Do not re-sort methods, group fields by guessed meaning, or otherwise "clean up" the layout unless an existing repo header or PS2 evidence proves a specific correction. ### 1e. Assembly reference @@ -130,6 +132,8 @@ and assembly: Utilize the dwarf information that you get from the lookup skill heavily. +For any recovered type you touch while implementing the function, treat the DWARF body as source material to copy, not prose to paraphrase. Start from the dumped layout in the canonical owner header, then make only the minimal verified fixes. + Don't add explanatory comments during implementation unless you need to document a remaining DWARF mismatch. Don't use any temporary local variables that don't exist in the dwarf. diff --git a/.github/skills/scaffold/SKILL.md b/.github/skills/scaffold/SKILL.md index 3fe3d9256..52cdf0e9a 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -29,7 +29,14 @@ Collect data from **all** of these sources in parallel where possible: ## Phase 2: Setup class -Copy and cleanup the header that you got from running the `lookup` skill using the `symbols/Dwarf` folder. Fix visibility, function order and vtable related things based on using `lookup` on the PS2 types. +Copy the header/type body that you got from running the `lookup` skill using the +`symbols/Dwarf` folder into the canonical owner header first. Do not retype or +reconstruct the layout from memory, from scattered callsites, or from guessed +semantics. + +Then do the minimum cleanup backed by evidence: fix visibility, function order and +vtable related things based on using `lookup` on the PS2 types, and clean up duplicated +inline copies when the DWARF emitted both versions. For formatting and local cleanup while writing the header, consult `.github/skills/code_style/SKILL.md`. Use it for member-comment alignment, declaration @@ -50,6 +57,11 @@ Preserve real member names, types, order, and offset comments while scaffolding. fill gaps with invented `pad`, `unk`, or `field_XXXX` members for game types; verify the layout from Dwarf / PS2 data and leave a TODO over the type if a field is still uncertain. +Preserve the declaration order from the dumped type body as well, not just the member +order. Do not regroup methods, fields, enums, or helper declarations for readability +unless an existing repo header or PS2 evidence proves the original owner header used a +different order. + Keep the `// total size: 0x...` comment above the recovered type declaration. When the recovered type is a `class`, keep explicit access sections and prefer putting methods / accessors before the member layout block unless existing repo evidence says otherwise. diff --git a/AGENTS.md b/AGENTS.md index 3e97d44b3..771ecd9a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -342,8 +342,15 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho - Inline assembly is acceptable when needed to reproduce dead code or compiler scheduling that source alone cannot express cleanly - Preserve the original `class` vs `struct` kind. Check existing headers first, then Dwarf / PS2 info when needed. Even forward declarations and local partial declarations should use the accurate keyword when known. - Prefer including the real repo header over introducing a local forward declaration for a project type. If a type already has a header in `src/`, include it instead of redeclaring it locally. -- If a subsystem already has a stub owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp`. +- If a subsystem already has a stub or umbrella owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp` or spinning up a one-off micro-header just for that type. +- Apply the same owner-header rule to shared enums, globals, callback typedefs, and free functions. If multiple TUs need the declaration, put it in the canonical owner header once and include that header instead of duplicating enum bodies or `extern` blocks across `.cpp`s. - Preserve original member names, types, order, and proven layout comments. Do not invent `pad`, `unk`, or `field_XXXX` members just to satisfy a guessed size or offset; verify the real members with `find-symbol.py`, GC Dwarf, and PS2 data, and leave a short TODO if a layout detail is still uncertain. +- When recovering a type, start by copying the GC DWARF struct/class body into the canonical owner header. Treat that dump as the source of truth for declaration order too; only apply targeted fixes that are backed by existing repo headers or PS2 data, such as visibility, virtual/function order, duplicate-inline cleanup, or owner-header placement. +- Do not hand-reconstruct recovered layouts from scattered field accesses, guessed semantics, or a "cleaned up" reordering. If you do not have enough evidence to paste the type confidently, stop and gather more DWARF / PS2 info first. +- Preserve the original scope and nesting of recovered declarations too. Keep class-owned enums/types nested when the original did, and move subsystem/global enums into their real owner header instead of flattening or duplicating them near one caller. +- Use the narrowest correct home for recovered declarations: shared project-facing types in headers, TU-private helper structs/classes and allocator metadata in the `.cpp`. Do not dump implementation-only helpers into public headers just because they were convenient to write there. +- Prefer real subsystem or vendor headers over ad-hoc local typedef/prototype blocks. If an external API is shared and the project is missing the proper header, add that header in the correct subtree instead of stashing declarations in an unrelated gameplay file. +- Do not leave repeated `// TODO move`, `// TODO where should this go`, or "I just made this up" markers around declarations. Either move the declaration to its owner now or leave one short targeted TODO above the owner declaration if ownership is still genuinely unresolved. - Follow DWARF member naming exactly (`mMember` vs `m_member`) instead of normalizing names - Omit the `this` pointer. - Use `nullptr` and `override`. If they are missing, you need to include `types.h`. @@ -381,7 +388,7 @@ A function is only done when both objdiff and normalized DWARF are exact. Treat 100% instruction match with a DWARF mismatch as unfinished work, not a near-complete result. -The dwarf of your structs doesn't have to neccessarily match the original due to various reasons, just make sure that you copied everything correctly. +The DWARF of your structs does not always compare cleanly in every detail, but the recovery process still starts by copying the dumped layout correctly. Do not freehand-reconstruct a struct from call sites or guessed semantics; paste the DWARF body into the real owner header first, then make only the minimal PS2/header-backed fixes such as visibility, function order, vtable order, or duplicate-inline cleanup. Never dismiss a diff as "close enough" or "just register allocation." Every mismatched instruction is a signal that the source doesn't perfectly represent the original. Even @@ -417,6 +424,8 @@ Virtual table layout is also missing from the dwarf but there on PS2. Be aware t The inline information in the dwarf is incredibly useful. When you encounter one, you should look up its body in the project. If it doesn't exist yet, deduce how the code should look like and add it to the correct header (you can use your address lookup skill or if that doesn't succeed and the inline is a member function, just find the corresponding class in the project). +For recovered structs and classes, treat DWARF as copied source material rather than a loose sketch. Paste the dumped type into the owner header first and keep its declaration/member order unless PS2 or an existing repo header proves a specific correction. + It's very important that you use math inlines from bMath and UMath as shown in the dwarf. UVector inlines use temporaries that the compiler couldn't optimize out. You can see in the dwarf on which stack address they are and deduce final destination they are copied to. ### Store instruction order hints From 9374685299caf3725b6779964f4eb5b27fa6ff3d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 12:27:34 +0100 Subject: [PATCH 124/372] 76.9%: inline deltaq cross-bin blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 271e828fd..4cc0e89c9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -324,10 +324,31 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *ceilQ = reinterpret_cast(&ceilq); + float *out = GetOutputQuat(sqt, boneIdxs[ibone]); DecodePhysical(ceilPhys[ibone], ceilq); - FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), - GetOutputQuat(sqt, boneIdxs[ibone])); + if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { + out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; + out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; + out[2] = scale * (ceilQ[2] - prevQ[2]) + prevQ[2]; + out[3] = scale * (ceilQ[3] - prevQ[3]) + prevQ[3]; + } else { + out[0] = prevQ[0] - scale * (ceilQ[0] + prevQ[0]); + out[1] = prevQ[1] - scale * (ceilQ[1] + prevQ[1]); + out[2] = prevQ[2] - scale * (ceilQ[2] + prevQ[2]); + out[3] = prevQ[3] - scale * (ceilQ[3] + prevQ[3]); + } + + { + float s = 1.0f / FastSqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]); + + out[0] *= s; + out[1] *= s; + out[2] *= s; + out[3] *= s; + } } } else { DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); From 1031956ecc1ea9652a26e03814eed028d071264d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 12:29:26 +0100 Subject: [PATCH 125/372] 76.9%: inline deltaq physical blends Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 4cc0e89c9..17ce54e80 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -547,10 +547,31 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *ceilQ = reinterpret_cast(&ceilq); + float *out = GetOutputQuat(sqt, boneIdxs[ibone]); DecodePhysical(ceilPhys[ibone], ceilq); - FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), - GetOutputQuat(sqt, boneIdxs[ibone])); + if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { + out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; + out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; + out[2] = scale * (ceilQ[2] - prevQ[2]) + prevQ[2]; + out[3] = scale * (ceilQ[3] - prevQ[3]) + prevQ[3]; + } else { + out[0] = prevQ[0] - scale * (ceilQ[0] + prevQ[0]); + out[1] = prevQ[1] - scale * (ceilQ[1] + prevQ[1]); + out[2] = prevQ[2] - scale * (ceilQ[2] + prevQ[2]); + out[3] = prevQ[3] - scale * (ceilQ[3] + prevQ[3]); + } + + { + float s = 1.0f / FastSqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]); + + out[0] *= s; + out[1] *= s; + out[2] *= s; + out[3] *= s; + } } } } else { From d59c4bd2a60f14ce82e3f896ce3f26f58b54cc99 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 12:32:51 +0100 Subject: [PATCH 126/372] 77.0%: inline deltaq masked same-bin blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 17ce54e80..1487c15d0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -581,6 +581,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; UMath::Vector4 ceilq; + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *ceilQ = reinterpret_cast(&ceilq); + float *out = GetOutputQuat(sqt, boneIdxs[ibone]); DecodeDelta(mMinRanges[ibone], *ceilDelta, delta); ceilq.x = mPrevQs[ibone].x + delta.x; @@ -588,8 +591,26 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq ceilq.z = mPrevQs[ibone].z + delta.z; RecoverW(ceilDelta->mW, ceilq); - FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), - GetOutputQuat(sqt, boneIdxs[ibone])); + if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { + out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; + out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; + out[2] = scale * (ceilQ[2] - prevQ[2]) + prevQ[2]; + out[3] = scale * (ceilQ[3] - prevQ[3]) + prevQ[3]; + } else { + out[0] = prevQ[0] - scale * (ceilQ[0] + prevQ[0]); + out[1] = prevQ[1] - scale * (ceilQ[1] + prevQ[1]); + out[2] = prevQ[2] - scale * (ceilQ[2] + prevQ[2]); + out[3] = prevQ[3] - scale * (ceilQ[3] + prevQ[3]); + } + + { + float s = 1.0f / FastSqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]); + + out[0] *= s; + out[1] *= s; + out[2] *= s; + out[3] *= s; + } } ceilDelta++; } From eadc13721c25a1e1b22ac4608d085347308741c1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 12:39:47 +0100 Subject: [PATCH 127/372] 77.0%: flatten masked deltaq physical decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 1487c15d0..57bf598a7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -551,7 +551,10 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq float *ceilQ = reinterpret_cast(&ceilq); float *out = GetOutputQuat(sqt, boneIdxs[ibone]); - DecodePhysical(ceilPhys[ibone], ceilq); + ceilQ[0] = ceilPhys[ibone].mX * kRangeScale15Bit - kFloatOne; + ceilQ[1] = ceilPhys[ibone].mY * kRangeScale16Bit - kFloatOne; + ceilQ[2] = ceilPhys[ibone].mZ * kRangeScale16Bit - kFloatOne; + RecoverW(ceilPhys[ibone].mW, ceilq); if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; From e3910f9238e3ca17905d2cd16dcf5f5040975558 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:20:47 +0100 Subject: [PATCH 128/372] 77.0%: delay stateless q anim load Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 5d0c248ae..3c6f51800 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -56,12 +56,12 @@ void FnStatelessQ::Eval(float, float currTime, float *sqt) { } bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { - StatelessQ *statelessQ = reinterpret_cast(mpAnim); - if (mUseFPS) { currTime *= mFPS; } + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + int floorTime = FloatToInt(currTime); int floorKey; bool slerpReqd; From 29e77a0b4286d17c0f9a6caccf1d8932f0b628c1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:21:53 +0100 Subject: [PATCH 129/372] 77.1%: delay stateless f3 anim load Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index d29cee6a9..ef8db4e03 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -44,12 +44,12 @@ void FnStatelessF3::Eval(float, float currTime, float *sqt) { } bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { - StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); - if (mUseFPS) { currTime *= mFPS; } + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + int floorTime = FloatToInt(currTime); int floorKey; bool slerpReqd; From e9eb914c1f1cbb6aa19703c1d3c837bdadb0022e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:23:35 +0100 Subject: [PATCH 130/372] 77.2%: reorder stateless f3 fast prev key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index ef8db4e03..7b593e24a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -248,6 +248,8 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo } bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + mPrevKey = static_cast(floorKey); + if (boneMask) { return EvalSQTMask(0.0f, sqt, boneMask, slerpReqd, floorKey, scale); } @@ -259,8 +261,6 @@ bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, boo short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); int nBones = statelessF3->mNumBones; - mPrevKey = static_cast(floorKey); - if (!slerpReqd) { for (int ibone = 0; ibone < nBones; ibone++) { int index = dofIdxs[ibone]; From ac0938e80a3597668732ead1f5f17e3ebf5c5ff2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:38:27 +0100 Subject: [PATCH 131/372] 77.2%: reorder loader init stores Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 6b7c04d12..6a37e3305 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -273,11 +273,11 @@ void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { h->symbols_num = 0; h->symtab = nullptr; h->sections = nullptr; + h->isOriginal = nullptr; + h->pSearchFunction = pSearchFunction; h->e = reinterpret_cast(mpData); h->chain = nullptr; - h->isOriginal = nullptr; h->mpDynamicLoader = this; - h->pSearchFunction = pSearchFunction; ELFHeader *e = h->e; From ea08ae4f17a75e49d269bb74c673fd3c26a3fd76 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:48:40 +0100 Subject: [PATCH 132/372] 77.2%: invert stateless f3 fast top branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 7b593e24a..1c7d88bc3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -250,58 +250,58 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { mPrevKey = static_cast(floorKey); - if (boneMask) { - return EvalSQTMask(0.0f, sqt, boneMask, slerpReqd, floorKey, scale); - } - - StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); - short *dataBuf = statelessF3->GetData(); - StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); - unsigned short *dofIdxs = statelessF3->mDofIdxs; - short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); - int nBones = statelessF3->mNumBones; + if (!boneMask) { + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; - if (!slerpReqd) { - for (int ibone = 0; ibone < nBones; ibone++) { - int index = dofIdxs[ibone]; + if (!slerpReqd) { + for (int ibone = 0; ibone < nBones; ibone++) { + int index = dofIdxs[ibone]; - sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; - sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; - sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; - frameData += 3; - } - } else { - short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } + } else { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); - for (int ibone = 0; ibone < nBones; ibone++) { - UMath::Vector3 prev; - UMath::Vector3 next; - int index = dofIdxs[ibone]; + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector3 prev; + UMath::Vector3 next; + int index = dofIdxs[ibone]; - UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); - UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); - sqt[index + 0] = prev.x + (next.x - prev.x) * scale; - sqt[index + 1] = prev.y + (next.y - prev.y) * scale; - sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; + sqt[index + 1] = prev.y + (next.y - prev.y) * scale; + sqt[index + 2] = prev.z + (next.z - prev.z) * scale; - frameData += 3; - nextFrameData += 3; + frameData += 3; + nextFrameData += 3; + } } - } - if (statelessF3->mNumConstBones != 0) { - unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); - float *constBuf = statelessF3->GetConstData(dataBuf); + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); - for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { - int index = constIdxs[ibone]; + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + int index = constIdxs[ibone]; - sqt[index + 0] = constBuf[0]; - sqt[index + 1] = constBuf[1]; - sqt[index + 2] = constBuf[2]; - constBuf += 3; + sqt[index + 0] = constBuf[0]; + sqt[index + 1] = constBuf[1]; + sqt[index + 2] = constBuf[2]; + constBuf += 3; + } } + } else { + EvalSQTMask(0.0f, sqt, boneMask, slerpReqd, floorKey, scale); } return true; From 3c9fa07eeb58a5b0e20dd5db0f7d4832694333e0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:50:41 +0100 Subject: [PATCH 133/372] 77.4%: invert stateless f3 fast slerp branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 1c7d88bc3..85848bc0a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -258,16 +258,7 @@ bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, boo short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); int nBones = statelessF3->mNumBones; - if (!slerpReqd) { - for (int ibone = 0; ibone < nBones; ibone++) { - int index = dofIdxs[ibone]; - - sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; - sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; - sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; - frameData += 3; - } - } else { + if (slerpReqd) { short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { @@ -285,6 +276,15 @@ bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, boo frameData += 3; nextFrameData += 3; } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + int index = dofIdxs[ibone]; + + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } } if (statelessF3->mNumConstBones != 0) { From 50fc49ec47e043c9397aa926fce5073948da1cc6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:55:22 +0100 Subject: [PATCH 134/372] 77.5%: delay stateless f3 fast output index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 85848bc0a..6edb8c2ee 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -132,11 +132,12 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask for (int ibone = 0; ibone < nBones; ibone++) { UMath::Vector3 prev; UMath::Vector3 next; - int index = dofIdxs[ibone]; UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + int index = dofIdxs[ibone]; + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; sqt[index + 1] = prev.y + (next.y - prev.y) * scale; sqt[index + 2] = prev.z + (next.z - prev.z) * scale; @@ -247,7 +248,7 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo return true; } -bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { +bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { mPrevKey = static_cast(floorKey); if (!boneMask) { @@ -301,7 +302,7 @@ bool FnStatelessF3::EvalSQTfast(float, float *sqt, const BoneMask *boneMask, boo } } } else { - EvalSQTMask(0.0f, sqt, boneMask, slerpReqd, floorKey, scale); + EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); } return true; From 1c2ae0e8fabead255be55fc2267ca4f2b584673f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 13:56:22 +0100 Subject: [PATCH 135/372] 77.5%: index stateless f3 const tail Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 6edb8c2ee..149661514 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -161,12 +161,12 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask float *constBuf = statelessF3->GetConstData(dataBuf); for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + float *constBone = &constBuf[ibone * 3]; int index = constIdxs[ibone]; - sqt[index + 0] = constBuf[0]; - sqt[index + 1] = constBuf[1]; - sqt[index + 2] = constBuf[2]; - constBuf += 3; + sqt[index + 0] = constBone[0]; + sqt[index + 1] = constBone[1]; + sqt[index + 2] = constBone[2]; } } } else { From b8342b48181a50ba651220ddc6aa79fc4173ec71 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:05:31 +0100 Subject: [PATCH 136/372] 77.5%: reorder deltaq init ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 57bf598a7..d77f437eb 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -189,10 +189,13 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (numBones != 0) { UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + + mMinRanges = minRanges; mPrevQs = prevQs; mPrevQBlock = prevQs; + } else { + mMinRanges = minRanges; } - mMinRanges = minRanges; } int floorTime = FloatToInt(currTime); int floorKey; @@ -402,10 +405,13 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (numBones != 0) { UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + + mMinRanges = minRanges; mPrevQs = prevQs; mPrevQBlock = prevQs; + } else { + mMinRanges = minRanges; } - mMinRanges = minRanges; } int floorTime = FloatToInt(currTime); int floorKey; From eaaa772158842c7bc6dd12a59ddeb4974461cd44 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:32:12 +0100 Subject: [PATCH 137/372] 77.5%: delay stateless f3 fast output index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 149661514..68e2c395d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -265,11 +265,12 @@ bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *bone for (int ibone = 0; ibone < nBones; ibone++) { UMath::Vector3 prev; UMath::Vector3 next; - int index = dofIdxs[ibone]; UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + int index = dofIdxs[ibone]; + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; sqt[index + 1] = prev.y + (next.y - prev.y) * scale; sqt[index + 2] = prev.z + (next.z - prev.z) * scale; From 1075140a820143cd0f82f88a487287d0310b5f1d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:33:02 +0100 Subject: [PATCH 138/372] 77.6%: index stateless f3 const tail Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 68e2c395d..53e4ad009 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -294,12 +294,12 @@ bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *bone float *constBuf = statelessF3->GetConstData(dataBuf); for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + float *constBone = &constBuf[ibone * 3]; int index = constIdxs[ibone]; - sqt[index + 0] = constBuf[0]; - sqt[index + 1] = constBuf[1]; - sqt[index + 2] = constBuf[2]; - constBuf += 3; + sqt[index + 0] = constBone[0]; + sqt[index + 1] = constBone[1]; + sqt[index + 2] = constBone[2]; } } } else { From b4270d5b1aece72f9e7f1918b937d1bab8fbc15d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:36:22 +0100 Subject: [PATCH 139/372] 77.6%: delay stateless f3 output index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 53e4ad009..f0fe1f316 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -200,11 +200,12 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector3 prev; UMath::Vector3 next; - int index = dofIdxs[ibone]; UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + int index = dofIdxs[ibone]; + sqt[index + 0] = prev.x + (next.x - prev.x) * scale; sqt[index + 1] = prev.y + (next.y - prev.y) * scale; sqt[index + 2] = prev.z + (next.z - prev.z) * scale; From 40ec80eb3de0f94c0f700a631c5c17df84e79770 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:46:39 +0100 Subject: [PATCH 140/372] 77.6%: shape qfast updater w bits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 25a6ecec8..209f60852 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -301,9 +301,9 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + nextQ[3] = static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * kQFastPhysicalScale12 - - kQFastPhysicalBias12; + kQFastPhysicalBias12; physical += 3; } } else { From e989a01531aa492aeef9729d300f4531311ffc35 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:48:16 +0100 Subject: [PATCH 141/372] 77.6%: mirror qfast updater w bits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 209f60852..2e094ff9d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -399,7 +399,7 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[3] = - static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } From 679f24ba069cbd2c88d629bbde15a9869797a6c7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:49:57 +0100 Subject: [PATCH 142/372] 77.7%: mirror masked qfast w bits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 2e094ff9d..2ebc87d96 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -712,7 +712,7 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + prevQ[3] = static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } @@ -810,7 +810,7 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) * 0x100) + ((physical[1] & 0xF) * 0x10))) * + out[3] = static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } From 0b64a49f61e17f91f86a90b375a9b848a8ff7ec0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 14:54:23 +0100 Subject: [PATCH 143/372] 77.7%: tighten deltaq and qfast init shapes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index d77f437eb..6c2ef2ace 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -191,8 +191,8 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); mMinRanges = minRanges; - mPrevQs = prevQs; mPrevQBlock = prevQs; + mPrevQs = prevQs; } else { mMinRanges = minRanges; } @@ -407,8 +407,8 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); mMinRanges = minRanges; - mPrevQs = prevQs; mPrevQBlock = prevQs; + mPrevQs = prevQs; } else { mMinRanges = minRanges; } From efac67f911f069633e07705796450da31de5b1fd Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:02:45 +0100 Subject: [PATCH 144/372] 77.7%: reorder loader init ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 6a37e3305..f431957df 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -266,6 +266,7 @@ void DynamicLoader::Resolve() { void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { HashPointer *h = reinterpret_cast(EAGL4Internal::EAGL4Malloc(sizeof(HashPointer), nullptr)); + h->pSearchFunction = pSearchFunction; h->next = nullptr; h->prev = nullptr; h->strtab = nullptr; @@ -273,10 +274,9 @@ void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { h->symbols_num = 0; h->symtab = nullptr; h->sections = nullptr; + h->chain = nullptr; h->isOriginal = nullptr; - h->pSearchFunction = pSearchFunction; h->e = reinterpret_cast(mpData); - h->chain = nullptr; h->mpDynamicLoader = this; ELFHeader *e = h->e; From 37251b2cba2e720d77f9efe82449ccab826dce66 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:06:42 +0100 Subject: [PATCH 145/372] 77.7%: reorder loader init pointers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index f431957df..69e34fef1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -276,8 +276,8 @@ void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { h->sections = nullptr; h->chain = nullptr; h->isOriginal = nullptr; - h->e = reinterpret_cast(mpData); h->mpDynamicLoader = this; + h->e = reinterpret_cast(mpData); ELFHeader *e = h->e; From 0e9495e06fa3658e349f22411e3d0e17334aa840 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:12:38 +0100 Subject: [PATCH 146/372] 77.8%: retime poseanim key search Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index c87f6b432..e73c115c8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -32,22 +32,23 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl unsigned int key = 0; bool interp = false; float t = 0.0f; + int floorTime = FloatToInt(currTime); - if (currTime > static_cast(times[0])) { + if (floorTime > times[0]) { unsigned int numKeys = poseAnim->mNumKeys; - if (currTime >= static_cast(times[numKeys - 1])) { + if (floorTime >= times[numKeys - 1]) { key = numKeys - 1; } else { key = mPrevKey; - if (key != 0 && static_cast(currTime) < times[key]) { + if (key != 0 && floorTime < times[key]) { do { key--; - } while (key > 0 && static_cast(currTime) < times[key]); - } else if (static_cast(currTime) >= times[key + 1]) { + } while (key > 0 && floorTime < times[key]); + } else if (floorTime >= times[key + 1]) { do { key++; - } while (key < numKeys - 2 && static_cast(currTime) >= times[key + 1]); + } while (key < numKeys - 2 && floorTime >= times[key + 1]); } if (numKeys != 0) { From c17711f246aa0d7168654c37e0a6a45377beeafc Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:14:25 +0100 Subject: [PATCH 147/372] 77.9%: restore poseanim float gate Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index e73c115c8..3f82c6927 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -34,10 +34,10 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl float t = 0.0f; int floorTime = FloatToInt(currTime); - if (floorTime > times[0]) { + if (currTime > static_cast(times[0])) { unsigned int numKeys = poseAnim->mNumKeys; - if (floorTime >= times[numKeys - 1]) { + if (currTime >= static_cast(times[numKeys - 1])) { key = numKeys - 1; } else { key = mPrevKey; From 3ed7fd62a7fae1727795ba47962e6fd220528874 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:19:20 +0100 Subject: [PATCH 148/372] 77.9%: shape poseanim key compare order Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 3f82c6927..578118140 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -41,10 +41,12 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl key = numKeys - 1; } else { key = mPrevKey; - if (key != 0 && floorTime < times[key]) { - do { - key--; - } while (key > 0 && floorTime < times[key]); + if (times[key] > floorTime) { + if (key != 0) { + do { + key--; + } while (key > 0 && floorTime < times[key]); + } } else if (floorTime >= times[key + 1]) { do { key++; From d22e67c2b0f4ff609584aa9e92c4187939ee6d16 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:32:33 +0100 Subject: [PATCH 149/372] 77.9%: tighten poseanim search compares Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 578118140..11f2e4680 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -45,12 +45,12 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl if (key != 0) { do { key--; - } while (key > 0 && floorTime < times[key]); + } while (key > 0 && times[key] > floorTime); } - } else if (floorTime >= times[key + 1]) { + } else if (times[key + 1] <= floorTime) { do { key++; - } while (key < numKeys - 2 && floorTime >= times[key + 1]); + } while (key < numKeys - 2 && times[key + 1] <= floorTime); } if (numKeys != 0) { From 25bf030b8c4d44b6e199129b99c77e5cd121e04f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:34:24 +0100 Subject: [PATCH 150/372] 77.9%: tighten statelessq search compares Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 3c6f51800..ad998df5a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -95,12 +95,12 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) if (mPrevKey != 0) { timeIndex = mPrevKey - 1; } - if (floorTime < statelessQ->mTimes[timeIndex]) { - while (timeIndex > 0 && floorTime < statelessQ->mTimes[timeIndex]) { + if (statelessQ->mTimes[timeIndex] > floorTime) { + while (timeIndex > 0 && statelessQ->mTimes[timeIndex] > floorTime) { timeIndex--; } } else { - while (timeIndex < statelessQ->mNumKeys - 2 && floorTime >= statelessQ->mTimes[timeIndex + 1]) { + while (timeIndex < statelessQ->mNumKeys - 2 && statelessQ->mTimes[timeIndex + 1] <= floorTime) { timeIndex++; } } From 32965006d1dc3078549a6bc4526befb0fb6ca924 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:47:07 +0100 Subject: [PATCH 151/372] 77.9%: reshape qfast no-times clamp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 2ebc87d96..32a0cad32 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -466,11 +466,10 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) if (!times) { if (floorTime < 0) { floorKey = 0; - } else { + } else if (deltaQ->mNumKeys > floorTime) { floorKey = floorTime; - if (deltaQ->mNumKeys <= floorTime) { - floorKey = deltaQ->mNumKeys - 1; - } + } else { + floorKey = deltaQ->mNumKeys - 1; } } else { if (floorTime < times[0]) { From 97e0c4d59e0519edbdf57c95a4b2557f6d428b76 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:48:04 +0100 Subject: [PATCH 152/372] 77.9%: tighten qfast floor-key clamp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 32a0cad32..0cb0b6076 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -121,10 +121,10 @@ static inline int FindQFastFloorKey(int prevKey, DeltaQFast *deltaQ, float currT if (floorTime < 0) { return 0; } - if (floorTime >= deltaQ->mNumKeys) { - return deltaQ->mNumKeys - 1; + if (deltaQ->mNumKeys > floorTime) { + return floorTime; } - return floorTime; + return deltaQ->mNumKeys - 1; } if (floorTime < deltaQ->mTimes[0]) { return 0; From 67d616e421bd9db4fd054624382bab3e0470de5b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 15:54:13 +0100 Subject: [PATCH 153/372] 78.0%: flip qfast timed-key branch ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 0cb0b6076..398ec8f32 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -480,16 +480,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) timeIndex = 0; } - if (floorTime < times[timeIndex]) { - if (timeIndex > 0) { - do { - timeIndex--; - if (timeIndex < 1) { - break; - } - } while (floorTime < times[timeIndex]); - } - } else { + if (floorTime >= times[timeIndex]) { int lastTimeIndex = deltaQ->mNumKeys - 2; if (timeIndex < lastTimeIndex) { unsigned int nextTime = times[timeIndex + 1]; @@ -501,6 +492,15 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) nextIndex = timeIndex; } } + } else { + if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { + break; + } + } while (floorTime < times[timeIndex]); + } } floorKey = timeIndex + 1; From 95db2232c3a62dcc64ea10fa1384a44953e5d02f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:25:07 +0100 Subject: [PATCH 154/372] 78.0%: tighten deltaq const remainder guards Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 7 +++---- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 6c2ef2ace..b25e213db 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -16,11 +16,10 @@ unsigned char *DeltaQ::GetConstBoneIdx() { s += numBones * sizeof(DeltaQMinRange); s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; - if (!remainder) { - return reinterpret_cast(s); + if (remainder != 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); } - s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); return reinterpret_cast(s); } @@ -34,7 +33,7 @@ DeltaQPhysical *DeltaQ::GetConstPhysical() { s += numBones * sizeof(DeltaQMinRange); s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; - if (remainder > 0) { + if (remainder != 0) { s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 398ec8f32..9cdc0349f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -42,11 +42,10 @@ unsigned char *DeltaQFast::GetConstBoneIdx() { s += numBones * sizeof(DeltaQFastMinRange); s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; - if (!remainder) { - return reinterpret_cast(s); + if (remainder != 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); } - s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); return reinterpret_cast(s); } @@ -60,7 +59,7 @@ DeltaQFastPhysical *DeltaQFast::GetConstPhysical() { s += numBones * sizeof(DeltaQFastMinRange); s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; - if (remainder > 0) { + if (remainder != 0) { s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); } From b735ea37eff25c400dec56196d0f244ea96855d6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:29:04 +0100 Subject: [PATCH 155/372] 78.0%: recover pose anim checksum override Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 9 ++++++++- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 11f2e4680..eb9065c59 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -7,10 +7,13 @@ namespace EAGL4Anim { -FnPoseAnim::FnPoseAnim() : mPrevKey(0) { +FnPoseAnim::FnPoseAnim() { + mPrevKey = 0; mType = AnimTypeId::ANIM_POSEANIM; } +FnPoseAnim::~FnPoseAnim() {} + void FnPoseAnim::SetAnimMemoryMap(AnimMemoryMap *anim) { mpAnim = anim; } @@ -138,4 +141,8 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl return true; } +unsigned short FnPoseAnim::GetTargetCheckSum() const { + return mpAnim->GetTargetCheckSum(); +} + }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h index 63c95b2e8..14e6405b9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h @@ -30,7 +30,7 @@ class FnPoseAnim : public FnAnimMemoryMap { } // Overrides: FnAnimSuper - ~FnPoseAnim() override {} + ~FnPoseAnim() override; static void PatchVtbl(FnPoseAnim *poseAnim) {} @@ -46,7 +46,7 @@ class FnPoseAnim : public FnAnimMemoryMap { bool EvalPose(float currTime, const PosePaletteBank *paletteBank, float *sqt) override; // Overrides: FnAnim - unsigned short GetTargetCheckSum() const override {} + unsigned short GetTargetCheckSum() const override; protected: unsigned short mPrevKey; // offset 0x10, size 0x2 From 690185ee107b6f44108d56917c934840a39a62cc Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:37:27 +0100 Subject: [PATCH 156/372] 78.0%: match pose anim length stub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index eb9065c59..2c325d9c6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -19,13 +19,9 @@ void FnPoseAnim::SetAnimMemoryMap(AnimMemoryMap *anim) { } bool FnPoseAnim::GetLength(float &timeLength) const { - const PoseAnim *poseAnim = reinterpret_cast(mpAnim); + int zero = 0; - if (!poseAnim->mTimes) { - timeLength = static_cast(poseAnim->mNumKeys - 1); - } else { - timeLength = static_cast(poseAnim->mTimes[poseAnim->mNumKeys - 1]); - } + timeLength = static_cast(zero); return true; } From e45675d0a73640111e91d3e3a295a5a5adf43c38 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:39:58 +0100 Subject: [PATCH 157/372] 78.0%: match pose anim constructor Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 2c325d9c6..29015956f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -8,8 +8,8 @@ namespace EAGL4Anim { FnPoseAnim::FnPoseAnim() { - mPrevKey = 0; mType = AnimTypeId::ANIM_POSEANIM; + mPrevKey = 0; } FnPoseAnim::~FnPoseAnim() {} From 108e94215135d01f0c46b3ddc11790a069c5443a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:41:01 +0100 Subject: [PATCH 158/372] 78.1%: match singleq eval wrapper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 57 +------------------ 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 8023e6292..320f27296 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -189,7 +189,7 @@ FnDeltaSingleQ::FnDeltaSingleQ() } void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { - EvalSQT(currTime, sqt, nullptr); + EvalSQTMasked(currTime, nullptr, sqt); } bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { @@ -617,59 +617,4 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo return true; } -void FnDeltaSingleQ::InitBuffersAsRequired() { - DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); - - mMinRanges = GetSingleQMinRanges(deltaQ); - mBins = GetSingleQBinStart(deltaQ); - mBinSize = GetSingleQBinSize(deltaQ); - - if (deltaQ->mNumBones != 0) { - float eul[3]; - - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; - mPreMultQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); - mPostMultQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); - - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DeltaSingleQMinRangef minRangef; - - DecodeSingleQMinRange(mMinRanges[ibone], minRangef); - - if (minRangef.mIndex == 0) { - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - eul[0] = kSingleQFloatZero; - eul[1] = minRangef.mConst0; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else if (minRangef.mIndex == 1) { - eul[0] = minRangef.mConst0; - eul[1] = kSingleQFloatZero; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - - eul[0] = kSingleQFloatZero; - eul[1] = kSingleQFloatZero; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else { - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - eul[0] = minRangef.mConst0; - eul[1] = minRangef.mConst1; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - } - } - } -} - }; // namespace EAGL4Anim From 8d228f2e8a5c0520b7dcdca45561758f975014f5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:43:41 +0100 Subject: [PATCH 159/372] 78.1%: fix qfast destructor block owner Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 9cdc0349f..ae7765220 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -184,8 +184,8 @@ FnDeltaQFast::FnDeltaQFast() } FnDeltaQFast::~FnDeltaQFast() { - if (mPrevQBlock) { - MemoryPoolManager::DeleteBlock(mPrevQBlock); + if (mMinRangesf) { + MemoryPoolManager::DeleteBlock(mMinRangesf); } } From e0e98de25f43faf86ba06299005ca120bf709059 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:44:28 +0100 Subject: [PATCH 160/372] 78.1%: match singleq destructor frees Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h index d84932b65..a165ed88c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h @@ -40,6 +40,8 @@ class FnDeltaSingleQ : public FnAnimMemoryMap { ~FnDeltaSingleQ() override { if (mPrevQBlock) { MemoryPoolManager::DeleteBlock(mPrevQBlock); + MemoryPoolManager::DeleteBlock(mPreMultQs); + MemoryPoolManager::DeleteBlock(mPostMultQs); } } From 887a7c442bbfc065c79f0ccdcc32b5990721a774 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:46:24 +0100 Subject: [PATCH 161/372] 78.1%: match cycle idx helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 6 ++++-- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 794799c46..159f32e9b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -351,10 +351,12 @@ int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const float length = endTime - startTime; if (t < startTime) { - return static_cast((startTime - t) / length); + t = startTime - t; + return static_cast(t / length); } if (t >= endTime) { - return static_cast((t - endTime) / length) + 1; + t -= endTime; + return static_cast(t / length) + 1; } return 0; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 184693bbc..25cd0378f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -218,10 +218,12 @@ int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) cons float length = endTime - startTime; if (t < startTime) { - return static_cast((startTime - t) / length); + t = startTime - t; + return static_cast(t / length); } if (t >= endTime) { - return static_cast((t - endTime) / length) + 1; + t -= endTime; + return static_cast(t / length) + 1; } return 0; } From a2a6e7287c805b01266e317d32d7b9dd508e445f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:51:27 +0100 Subject: [PATCH 162/372] 78.2%: tighten blender velocity guards Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 5 +---- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 159f32e9b..45b0f757d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -228,9 +228,6 @@ bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { float vel0[2]; float vel1[2]; - if (!mFnVelAnims[0] || !mFnVelAnims[1]) { - return false; - } if (!mFnVelAnims[0]->EvalVel2D(t0, vel0)) { return false; } @@ -251,8 +248,8 @@ bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { float vel0Length = length(vel0); float scale = (mWeight * vel1Length + weight1 * vel0Length) / velLength; - vel[1] = vel[1] * scale; vel[0] = vel[0] * scale; + vel[1] = vel[1] * scale; } } else { vel[1] = vel0[1]; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 25cd0378f..eab0d1037 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -164,9 +164,6 @@ bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { float vel0[2]; float vel1[2]; - if (!mFnAnims[0] || !mFnAnims[1]) { - return false; - } if (!mFnAnims[0]->EvalVel2D(t0, vel0)) { return false; } @@ -187,8 +184,8 @@ bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { float vel0Length = length(vel0); float scale = (mWeight * vel1Length + weight1 * vel0Length) / velLength; - vel[1] = vel[1] * scale; vel[0] = vel[0] * scale; + vel[1] = vel[1] * scale; } } else { vel[1] = vel0[1]; From d9a0ec61b6a29e39fb1d614edf376e4eef66ffd7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 16:58:33 +0100 Subject: [PATCH 163/372] 78.2%: match phase channel length Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp index ec0b62fd0..17ff9ae14 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp @@ -6,7 +6,7 @@ namespace EAGL4Anim { bool FnPhaseChan::GetLength(float &timeLength) const { const PhaseChan *phaseChan = reinterpret_cast(mpAnim); - timeLength = static_cast(phaseChan->mNumFrames - 1); + timeLength = static_cast(phaseChan->mNumFrames); return true; } From c98d94ae0304e942dfee51a959ecd4547964ba0b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:02:20 +0100 Subject: [PATCH 164/372] 78.2%: match phase channel setup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp index 17ff9ae14..e5e094d71 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp @@ -12,24 +12,12 @@ bool FnPhaseChan::GetLength(float &timeLength) const { void FnPhaseChan::SetAnimMemoryMap(AnimMemoryMap *anim) { PhaseChan *phaseChan = reinterpret_cast(anim); - unsigned char flag = phaseChan->mFlag; mpAnim = anim; mIdx = 0; mCurrentFrame = phaseChan->mStartTime; - mRight = (flag & 1) == 0; - - if ((flag & 0x8) != 0) { - mSampleRate = 1; - } else if ((flag & 0x10) != 0) { - mSampleRate = 2; - } else if ((flag & 0x20) != 0) { - mSampleRate = 4; - } else if ((flag & 0x40) != 0) { - mSampleRate = 8; - } else { - mSampleRate = 1; - } + mRight = !phaseChan->StartWithRight(); + mSampleRate = phaseChan->GetAngleSampleRate(); } void FnPhaseChan::Eval(float prevTime, float currTime, float *phaseValue) { From 2191caf3ac6fa5f21c692250ccab15197079285e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:12:00 +0100 Subject: [PATCH 165/372] 78.3%: inline blender align lengths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 6 ++++-- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 45b0f757d..2173fd04d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -360,12 +360,14 @@ int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { float dot = v1[0] * v2[0] + v1[1] * v2[1]; - float norm = length(v1) * length(v2); + float v1Length = sqrtf(v1[0] * v1[0] + v1[1] * v1[1]); + float v2Length = sqrtf(v2[0] * v2[0] + v2[1] * v2[1]); + float norm = v1Length * v2Length; float w = ((dot / norm) + 1.0f) * 0.5f; q.x = 0.0f; - q.z = 0.0f; q.y = sqrtf(1.0f - w); + q.z = 0.0f; q.w = sqrtf(w); if (0.0f < v1[0] * v2[1] - v1[1] * v2[0]) { q.y = -q.y; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index eab0d1037..5ea11bd97 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -227,12 +227,14 @@ int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) cons void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { float dot = v1[0] * v2[0] + v1[1] * v2[1]; - float norm = turnLength(v1) * turnLength(v2); + float v1Length = sqrtf(v1[0] * v1[0] + v1[1] * v1[1]); + float v2Length = sqrtf(v2[0] * v2[0] + v2[1] * v2[1]); + float norm = v1Length * v2Length; float w = ((dot / norm) + 1.0f) * 0.5f; q.x = 0.0f; - q.z = 0.0f; q.y = sqrtf(1.0f - w); + q.z = 0.0f; q.w = sqrtf(w); if (0.0f < v1[0] * v2[1] - v1[1] * v2[0]) { q.y = -q.y; From 840780610ed18f728e72ea22419ea4cd01344ac9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:19:49 +0100 Subject: [PATCH 166/372] 78.4%: trim blender constructor stores Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 31 ++++++++----------- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 29 ++++++----------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 2173fd04d..0b2ef68a0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -15,28 +15,23 @@ float length(float *v) { namespace EAGL4Anim { FnRunBlender::FnRunBlender() - : mAnims(nullptr), // - mPhases(nullptr), // - mVels(nullptr), // - mWeight(0.0f), // - mNumAnims(0), // - mIdx(-100), // - mSkeleton(nullptr), // - mFreq(1.0f), // - mPrevTime(0.0f), // - mOffset(0.0f), // - mCycleIdx(-100), // - mInit(false), // - mSpeed(nullptr) { + : mFreq(1.0f), // + mOffset(0.0f), // + mCycleIdx(-100) { + mFnVelAnims[1] = nullptr; mType = AnimTypeId::ANIM_RUNBLENDER; + mAnims = nullptr; + mPhases = nullptr; + mVels = nullptr; + mWeight = 0.0f; + mNumAnims = 0; + mIdx = -100; + mPrevTime = 0.0f; + mInit = false; + mSpeed = nullptr; mFnAnims[0] = nullptr; mFnAnims[1] = nullptr; mFnVelAnims[0] = nullptr; - mFnVelAnims[1] = nullptr; - mAlignFrame[0] = 0.0f; - mAlignFrame[1] = 0.0f; - mCycles[0] = 0.0f; - mCycles[1] = 0.0f; } FnRunBlender::~FnRunBlender() { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 5ea11bd97..b13cd996a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -17,27 +17,18 @@ static int i_6840; namespace EAGL4Anim { FnTurnBlender::FnTurnBlender() - : mAnims(nullptr), // - mWeight(0.0f), // - mNumAnims(0), // - mIdx(-100), // - mSkeleton(nullptr), // - mFreq(1.0f), // - mPrevTime(0.0f), // - mOffset(0.0f), // - mCycleIdx(-100), // - mInit(false) { + : mFreq(1.0f), // + mOffset(0.0f), // + mCycleIdx(-100) { + mFnAnims[1] = nullptr; mType = AnimTypeId::ANIM_TURNBLENDER; + mAnims = nullptr; + mWeight = 0.0f; + mNumAnims = 0; + mIdx = -100; + mPrevTime = 0.0f; + mInit = false; mFnAnims[0] = nullptr; - mFnAnims[1] = nullptr; - mCycles[0] = 0.0f; - mCycles[1] = 0.0f; - mOffsets[0] = 0.0f; - mOffsets[1] = 0.0f; - mAlignQ.x = 0.0f; - mAlignQ.y = 0.0f; - mAlignQ.z = 0.0f; - mAlignQ.w = 1.0f; } FnTurnBlender::~FnTurnBlender() { From bb2cb45aee7c271661912880ee8f67fbf858a779 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:26:39 +0100 Subject: [PATCH 167/372] 78.4%: tighten raw pose frame output ownership Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index c06d4c4c1..0cf219e46 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -126,12 +126,12 @@ void RawPoseChannel::EvalFrame(int frame, float *outputPose, const BoneMask *bon if (!boneMask) { while (sig < sigEnd) { int numChannels = *sig++; - float *bonePose = outputPose; + float *nextOutputPose = outputPose + 12; - outputPose = bonePose + 12; for (int ichan = 0; ichan < numChannels; ichan++) { - reinterpret_cast(*sig++)(frameData, bonePose + 4); + reinterpret_cast(*sig++)(frameData, outputPose + 4); } + outputPose = nextOutputPose; } } else { for (unsigned int ibone = 0; sig < sigEnd; ibone++) { From 584d6e4b13f2b31d58eca2cf046900e03bd81cc4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:32:10 +0100 Subject: [PATCH 168/372] 78.4%: match stateless constructors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 6 +----- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 5 +---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index f0fe1f316..60281414b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -18,12 +18,8 @@ static inline void UnquantizeStatelessF3(const StatelessF3::DofInfo &dofInfo, co } // namespace -FnStatelessF3::FnStatelessF3() - : mUseFPS(false), // - mFPS(0), // - mBoneMask(nullptr) { +FnStatelessF3::FnStatelessF3() { mType = AnimTypeId::ANIM_STATELESSF3; - mPrevKey = 0; } FnStatelessF3::~FnStatelessF3() {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index ad998df5a..1849f5c86 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -30,10 +30,7 @@ static inline void LoadStatelessQ(unsigned short *frameData, UMath::Vector4 &q) } // namespace -FnStatelessQ::FnStatelessQ() - : mUseFPS(false), // - mFPS(0), // - mBoneMask(nullptr) { +FnStatelessQ::FnStatelessQ() { mType = AnimTypeId::ANIM_STATELESSQ; mPrevKey = 0; } From 21cc78506b13e62bbd8bf6349c5174ad9e3f1ff9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:40:05 +0100 Subject: [PATCH 169/372] 78.4%: fix pose anim dof indexing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 29015956f..52d5295d5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -83,7 +83,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl while (transIdx >= 0) { float *from = &poseData[src0]; float *to = &poseData[src1]; - float *out = &sqt[dofIndices[numQ + transIdx] * 4]; + float *out = &sqt[dofIndices[numQ + transIdx]]; out[0] = from[0] + (to[0] - from[0]) * t; out[1] = from[1] + (to[1] - from[1]) * t; @@ -97,7 +97,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl int quatIdx = static_cast(numQ) - 1; while (quatIdx >= 0) { - FastQuatBlendF4(t, &poseData[src0], &poseData[src1], &sqt[dofIndices[quatIdx] * 4]); + FastQuatBlendF4(t, &poseData[src0], &poseData[src1], &sqt[dofIndices[quatIdx]]); src0 -= 4; src1 -= 4; quatIdx--; @@ -108,7 +108,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl while (transIdx >= 0) { float *from = &poseData[src]; - float *out = &sqt[dofIndices[numQ + transIdx] * 4]; + float *out = &sqt[dofIndices[numQ + transIdx]]; out[0] = from[0]; out[1] = from[1]; @@ -122,7 +122,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl while (quatIdx >= 0) { float *from = &poseData[src]; - float *out = &sqt[dofIndices[quatIdx] * 4]; + float *out = &sqt[dofIndices[quatIdx]]; out[0] = from[0]; out[1] = from[1]; From b466cb65543019949531811da0a9a73a75999d00 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:46:01 +0100 Subject: [PATCH 170/372] 78.4%: delay stateless q output index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 1849f5c86..40d288b23 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -140,11 +140,12 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) for (int ibone = 0; ibone < nBones; ibone++) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); LoadStatelessQ(frameData, prevQ); LoadStatelessQ(nextFrameData, nextQ); + float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); + q[0] = prevQ.x + (nextQ.x - prevQ.x) * scale; q[1] = prevQ.y + (nextQ.y - prevQ.y) * scale; q[2] = prevQ.z + (nextQ.z - prevQ.z) * scale; From d7b409895c3ab5a29de24f592b34ca3479d00332 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:50:58 +0100 Subject: [PATCH 171/372] 78.4%: retime stateless q blend stores Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 40d288b23..407abd2c0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -144,12 +144,16 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) LoadStatelessQ(frameData, prevQ); LoadStatelessQ(nextFrameData, nextQ); + float x = prevQ.x + (nextQ.x - prevQ.x) * scale; + float y = prevQ.y + (nextQ.y - prevQ.y) * scale; + float z = prevQ.z + (nextQ.z - prevQ.z) * scale; + float w = prevQ.w + (nextQ.w - prevQ.w) * scale; float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); - q[0] = prevQ.x + (nextQ.x - prevQ.x) * scale; - q[1] = prevQ.y + (nextQ.y - prevQ.y) * scale; - q[2] = prevQ.z + (nextQ.z - prevQ.z) * scale; - q[3] = prevQ.w + (nextQ.w - prevQ.w) * scale; + q[0] = x; + q[1] = y; + q[2] = z; + q[3] = w; frameData += 4; nextFrameData += 4; From b5a9238508e502953df9e4d79194ccdafd005bea Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Mon, 23 Mar 2026 17:54:11 +0100 Subject: [PATCH 172/372] Fix most of the function order in zTrack --- src/Speed/Indep/SourceLists/zTrack.cpp | 6 +- src/Speed/Indep/Src/World/EventManager.cpp | 186 +- src/Speed/Indep/Src/World/Scenery.cpp | 1056 +++--- src/Speed/Indep/Src/World/ScreenEffects.cpp | 202 +- src/Speed/Indep/Src/World/Track.cpp | 42 +- src/Speed/Indep/Src/World/TrackInfo.cpp | 22 +- src/Speed/Indep/Src/World/TrackPath.cpp | 40 +- .../Indep/Src/World/TrackPositionMarker.cpp | 18 +- src/Speed/Indep/Src/World/TrackStreamer.cpp | 2922 ++++++++--------- src/Speed/Indep/Src/World/VisibleSection.cpp | 940 +++--- src/Speed/Indep/Src/World/WeatherMan.cpp | 168 +- 11 files changed, 2810 insertions(+), 2792 deletions(-) diff --git a/src/Speed/Indep/SourceLists/zTrack.cpp b/src/Speed/Indep/SourceLists/zTrack.cpp index 6bc74464d..b627cab70 100644 --- a/src/Speed/Indep/SourceLists/zTrack.cpp +++ b/src/Speed/Indep/SourceLists/zTrack.cpp @@ -16,10 +16,10 @@ #include "Speed/Indep/Src/World/VisibleSection.cpp" -#include "Speed/Indep/Src/World/ScreenEffects.cpp" - #include "Speed/Indep/Src/World/WeatherMan.cpp" -#include "Speed/Indep/Src/World/ParameterMaps.cpp" +#include "Speed/Indep/Src/World/ScreenEffects.cpp" #include "Speed/Indep/Src/World/EventManager.cpp" + +#include "Speed/Indep/Src/World/ParameterMaps.cpp" diff --git a/src/Speed/Indep/Src/World/EventManager.cpp b/src/Speed/Indep/Src/World/EventManager.cpp index 865252fd6..705c0b89d 100644 --- a/src/Speed/Indep/Src/World/EventManager.cpp +++ b/src/Speed/Indep/Src/World/EventManager.cpp @@ -177,66 +177,6 @@ void emEventManagerInit() { EventHandlerSlotPool = bNewSlotPool(0x18, 0x14, "EventHandlerSlotPool", 0); } -int emAddHandler(EVENT_HANDLER_FUNC function, unsigned int stream_mask) { - if (function && stream_mask) { - for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); - handler = handler->GetNext()) { - if (handler->HandlerFunction == function) { - handler->ReferenceCount += 1; - return 1; - } - } - - emEventHandler *handler = reinterpret_cast(bOMalloc(EventHandlerSlotPool)); - if (!handler) { - return 0; - } - - handler->HandlerFunction = function; - handler->StreamMask = stream_mask; - handler->ReferenceCount = 1; - EventHandlerList.AddTail(handler); - EventManagerStats[1] += 1; - if (EventManagerStats[1] > EventManagerStats[4]) { - EventManagerStats[4] = EventManagerStats[1]; - } - return 1; - } - - return 0; -} - -void emRemoveHandler(EVENT_HANDLER_FUNC function) { - for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); - handler = handler->GetNext()) { - if (handler->HandlerFunction == function) { - int ref_count = handler->ReferenceCount - 1; - handler->ReferenceCount = ref_count; - if (ref_count == 0) { - if (handler->Remove()) { - bFree(EventHandlerSlotPool, handler); - } - EventManagerStats[1] -= 1; - } - return; - } - } -} - -emEvent *emAddEvent(EVENT_ID event_id) { - emEvent *event = new emEvent; - if (!event) { - return 0; - } - - bMemSet(event, 0, sizeof(emEvent)); - event->ReferenceCount = 0; - event->ID = event_id; - CurrentEventQueue->AddTail(event); - EventManagerStats[0] += 1; - return event; -} - int LoaderEventManager(bChunk *bchunk) { if (bchunk->GetID() != 0x80036000) { return false; @@ -338,50 +278,64 @@ int UnloaderEventManager(bChunk *bchunk) { return true; } -emEvent **emTriggerEventsInSection(bVector3 *position, int section_number) { - emEvent **current_event = TriggerEventArray; - emEvent **sentinel_event = &TriggerEventArray[40]; - float x = position->x; - float y = position->y; - float z = position->z; - VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); +int emAddHandler(EVENT_HANDLER_FUNC function, unsigned int stream_mask) { + if (function && stream_mask) { + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + if (handler->HandlerFunction == function) { + handler->ReferenceCount += 1; + return 1; + } + } - if (user_info && user_info->pEventTriggerPack) { - EventTriggerPack *trigger_pack = user_info->pEventTriggerPack; - vAABBTree *tree = trigger_pack->EventTree; - vAABB *aabb = tree->QueryLeaf(x, y, z); - if (aabb) { - EventTrigger *root_event = trigger_pack->EventTriggerArray; - int num_hits = -aabb->NumChildren; + emEventHandler *handler = reinterpret_cast(bOMalloc(EventHandlerSlotPool)); + if (!handler) { + return 0; + } - for (int i = 0; i < num_hits && current_event < sentinel_event; i++) { - EventTrigger *event = &root_event[aabb->ChildrenIndicies[i]]; - float event_x = event->PositionX; - float event_z = event->PositionZ; - float event_y = event->PositionY; - float dz = bAbs(z - event_z); - float dy = bAbs(y - event_y); - float dx = bAbs(x - event_x); - float r2 = event->GetRadius(); - float dist2 = dz * dz + dx * dx + dy * dy; + handler->HandlerFunction = function; + handler->StreamMask = stream_mask; + handler->ReferenceCount = 1; + EventHandlerList.AddTail(handler); + EventManagerStats[1] += 1; + if (EventManagerStats[1] > EventManagerStats[4]) { + EventManagerStats[4] = EventManagerStats[1]; + } + return 1; + } - r2 *= r2; - if (dist2 < r2) { - emEvent *new_event = emAddEvent(static_cast(event->GetEventID())); - new_event->pEventTrigger = event; - *current_event = new_event; - current_event++; + return 0; +} + +void emRemoveHandler(EVENT_HANDLER_FUNC function) { + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + if (handler->HandlerFunction == function) { + int ref_count = handler->ReferenceCount - 1; + handler->ReferenceCount = ref_count; + if (ref_count == 0) { + if (handler->Remove()) { + bFree(EventHandlerSlotPool, handler); } + EventManagerStats[1] -= 1; } + return; } } +} - if (current_event == TriggerEventArray) { +emEvent *emAddEvent(EVENT_ID event_id) { + emEvent *event = new emEvent; + if (!event) { return 0; } - *current_event = 0; - return TriggerEventArray; + bMemSet(event, 0, sizeof(emEvent)); + event->ReferenceCount = 0; + event->ID = event_id; + CurrentEventQueue->AddTail(event); + EventManagerStats[0] += 1; + return event; } void emProcessAllEvents() { @@ -432,3 +386,49 @@ void emProcessAllEvents() { CurrentEventQueue = &MasterEventQueue; } +emEvent **emTriggerEventsInSection(bVector3 *position, int section_number) { + emEvent **current_event = TriggerEventArray; + emEvent **sentinel_event = &TriggerEventArray[40]; + float x = position->x; + float y = position->y; + float z = position->z; + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); + + if (user_info && user_info->pEventTriggerPack) { + EventTriggerPack *trigger_pack = user_info->pEventTriggerPack; + vAABBTree *tree = trigger_pack->EventTree; + vAABB *aabb = tree->QueryLeaf(x, y, z); + if (aabb) { + EventTrigger *root_event = trigger_pack->EventTriggerArray; + int num_hits = -aabb->NumChildren; + + for (int i = 0; i < num_hits && current_event < sentinel_event; i++) { + EventTrigger *event = &root_event[aabb->ChildrenIndicies[i]]; + float event_x = event->PositionX; + float event_z = event->PositionZ; + float event_y = event->PositionY; + float dz = bAbs(z - event_z); + float dy = bAbs(y - event_y); + float dx = bAbs(x - event_x); + float r2 = event->GetRadius(); + float dist2 = dz * dz + dx * dx + dy * dy; + + r2 *= r2; + if (dist2 < r2) { + emEvent *new_event = emAddEvent(static_cast(event->GetEventID())); + new_event->pEventTrigger = event; + *current_event = new_event; + current_event++; + } + } + } + } + + if (current_event == TriggerEventArray) { + return 0; + } + + *current_event = 0; + return TriggerEventArray; +} + diff --git a/src/Speed/Indep/Src/World/Scenery.cpp b/src/Speed/Indep/Src/World/Scenery.cpp index f3ffaef58..e6f7fe217 100644 --- a/src/Speed/Indep/Src/World/Scenery.cpp +++ b/src/Speed/Indep/Src/World/Scenery.cpp @@ -283,13 +283,6 @@ SceneryOverrideInfo *GetSceneryOverrideInfo(int override_info_number) { return reinterpret_cast(reinterpret_cast(SceneryOverrideInfoTable) + override_info_number * 6); } -void SceneryOverrideInfo::AssignOverrides() { - ScenerySectionHeader *section_header = GetScenerySectionHeader(SectionNumber); - if (section_header) { - AssignOverrides(section_header); - } -} - void SceneryOverrideInfo::AssignOverrides(ScenerySectionHeader *section_header) { SceneryInstance *scenery_instance = section_header->GetSceneryInstance(InstanceNumber); @@ -309,6 +302,40 @@ void SceneryOverrideInfo::AssignOverrides(ScenerySectionHeader *section_header) scenery_instance->ExcludeFlags = ExcludeFlags + (scenery_instance->ExcludeFlags & 0xFFFF0000); } +void SceneryOverrideInfo::AssignOverrides() { + ScenerySectionHeader *section_header = GetScenerySectionHeader(SectionNumber); + if (section_header) { + AssignOverrides(section_header); + } +} + +void LoadPrecullerBooBooScript(const char *filename, bool reset) { + if (reset) { + gPrecullerBooBooManager.Reset(); + } + + SpeedScript script(filename, 1); + while (script.GetNextCommand("BOOBOO:")) { + if (bStrICmp(script.GetNextArgumentString(), TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { + script.GetNextArgumentString(); + char *option = script.GetNextArgumentString(); + bool set_booboo = bStrICmp(option, "SET") == 0; + bool clr_booboo = bStrICmp(option, "CLR") == 0; + script.GetNextArgumentString(); + bVector3 pos = script.GetNextArgumentVector3(); + if (set_booboo) { + gPrecullerBooBooManager.Set(pos); + } else if (clr_booboo) { + gPrecullerBooBooManager.Clr(pos); + } + } + } +} + +void LoadPrecullerBooBooScripts() { + LoadPrecullerBooBooScript("TRACKS\\PrecullerBooBooScript.hoo", 1); +} + int LoaderSceneryGroup(bChunk *chunk) { if (chunk->GetID() == 0x34109) { int chunk_size = chunk->Size; @@ -407,365 +434,63 @@ void DisableAllSceneryGroups() { } } -void InitVisibleZones() { - if (pVisibleZoneBoundaryModel == 0) { - eModel *model = reinterpret_cast(bOMalloc(eModelSlotPool)); - unsigned int name_hash = bStringHash("MARKER_BOUNDARY"); - model->NameHash = 0; - model->Solid = 0; - model->Init(name_hash); - pVisibleZoneBoundaryModel = model; - } -} - -void CloseVisibleZones() { - eModel *model = pVisibleZoneBoundaryModel; - if (pVisibleZoneBoundaryModel) { - pVisibleZoneBoundaryModel->UnInit(); - bFree(eModelSlotPool, model); - } - pVisibleZoneBoundaryModel = 0; - if (SeeulatorToolActive) { - int data = 0; - bFunkCallASync("Seeulator", 4, &data, 4); - bFunkCallASync("Seeulator", 5, &data, 4); - bFunkCallASync("Seeulator", 6, &data, 4); - } -} - -void ServicePreculler() {} - -void LoadPrecullerBooBooScript(const char *filename, bool reset) { - if (reset) { - gPrecullerBooBooManager.Reset(); - } - - SpeedScript script(filename, 1); - while (script.GetNextCommand("BOOBOO:")) { - if (bStrICmp(script.GetNextArgumentString(), TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { - script.GetNextArgumentString(); - char *option = script.GetNextArgumentString(); - bool set_booboo = bStrICmp(option, "SET") == 0; - bool clr_booboo = bStrICmp(option, "CLR") == 0; - script.GetNextArgumentString(); - bVector3 pos = script.GetNextArgumentVector3(); - if (set_booboo) { - gPrecullerBooBooManager.Set(pos); - } else if (clr_booboo) { - gPrecullerBooBooManager.Clr(pos); - } - } - } -} - -void LoadPrecullerBooBooScripts() { - LoadPrecullerBooBooScript("TRACKS\\PrecullerBooBooScript.hoo", 1); -} - -SceneryInfo *FindSceneryInfo(unsigned int name_hash) { - for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); - section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); - section_header = reinterpret_cast(section_header->GetNext())) { - int *section_header_words = reinterpret_cast(section_header); - for (int i = 0; i < section_header_words[7]; i++) { - SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + i; - eModel *model = scenery_info->pModel[0]; - if (model && model->NameHash == name_hash) { - return scenery_info; - } - } - } - return 0; -} - -SceneryInstance *FindSceneryInstance(unsigned int name_hash) { - for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); - section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); - section_header = reinterpret_cast(section_header->GetNext())) { - int *section_header_words = reinterpret_cast(section_header); - for (int i = 0; i < section_header_words[9]; i++) { - SceneryInstance *instance = reinterpret_cast(section_header_words[8]) + i; - SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + instance->SceneryInfoNumber; - eModel *model = scenery_info->pModel[0]; - if (model && model->NameHash == name_hash) { - return instance; - } - } +ScenerySectionHeader *GetScenerySectionHeader(int section_number) { + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); + if (!user_info) { + return 0; } - return 0; + return user_info->pScenerySectionHeader; } -void ScenerySectionHeader::DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state) { - int *section_header_words = reinterpret_cast(this); - SceneryInstance *instance = GetSceneryInstance(scenery_instance_number); - tPrecullerInfo *preculler_info = GetPrecullerInfo(instance->PrecullerInfoIndex); - int preculler_section_number = scenery_cull_info->PrecullerSectionNumber; - if (preculler_section_number >= 0) { - int byte_number = preculler_section_number >> 3; - int bit_number = preculler_section_number & 7; - unsigned char visibility_bits = preculler_info->GetBits()[byte_number]; - int visibility_mask = 1 << bit_number; - if ((visibility_bits & visibility_mask) != 0) { - return; +int LoaderScenery(bChunk *chunk) { + if (chunk->GetID() == 0x34108) { + SceneryOverrideInfoTable = reinterpret_cast(chunk->GetData()); + NumSceneryOverrideInfos = static_cast(chunk->Size) / 6; + for (int i = 0; i < NumSceneryOverrideInfos; i++) { + SceneryOverrideInfo *override_info = &SceneryOverrideInfoTable[i]; + override_info->EndianSwap(); } + BuildSceneryOverrideHashTable(); + return 1; } - unsigned char instance_exclude_flags = instance->ExcludeFlags; - short scenery_info_number = instance->SceneryInfoNumber; - if (((instance_exclude_flags ^ 0x60) & scenery_cull_info->ExcludeFlags) != 0) { - return; - } - int pixel_size_int; - SceneryInfo *scenery_info = reinterpret_cast(GetSceneryInfo_Scenery(section_header_words, scenery_info_number)); - - if (visibility_state == EVISIBLESTATE_PARTIAL) { - bVector3 bbox_min; - bVector3 bbox_max; - instance->GetBBox(&bbox_min, &bbox_max); - visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); - if (visibility_state == EVISIBLESTATE_NOT) { - return; - } - } + if (chunk->GetID() == 0x80034100) { + ScenerySectionHeader *section_header = 0; + bChunk *last_chunk = chunk->GetLastChunk(); - float radius = scenery_info->Radius + 6.0f; - pixel_size_int = InlinedViewGetPixelSize(scenery_cull_info, instance->GetPosition(), radius); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + unsigned int subchunk_id = subchunk->GetID(); + if (subchunk_id == 0x34101) { + section_header = reinterpret_cast(subchunk->GetAlignedData(0x10)); + int *section_header_words = reinterpret_cast(section_header); + if (section_header_words[2] == 0) { + bEndianSwap32(reinterpret_cast(section_header_words) + 0xC); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x10); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x14); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x38); + } - if (pixel_size_int < 2) { - return; - } - unsigned int instance_flags = instance->ExcludeFlags; - if ((instance_flags & 0x2000000) != 0) { - pixel_size_int += 10; - } + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.AllocateUserInfo(section_header_words[3]); + user_info->pScenerySectionHeader = section_header; - eModel *model = 0; - if ((scenery_cull_info->ExcludeFlags & 0x1800) != 0) { - if ((instance_flags & 0x80) != 0) { - if (pixel_size_int > 0x1F) { - model = scenery_info->pModel[2]; - } - } else { - if (pixel_size_int > 0x1F) { - if ((instance_flags & 0x1000100) != 0) { - model = scenery_info->pModel[0]; + if (GetScenerySectionLetter(section_header_words[3]) == 'Z') { + ScenerySectionHeaderList.AddHead(section_header); } else { - model = scenery_info->pModel[3]; - } - } - } - } else if ((scenery_cull_info->ExcludeFlags & 0x20) != 0) { - if (pixel_size_int > 0x1F) { - model = scenery_info->pModel[2]; - } - } else if (eGetCurrentViewMode() > EVIEWMODE_ONE_RVM) { - if (pixel_size_int > 0x16) { - model = scenery_info->pModel[2]; - } - } else if (pixel_size_int > 0x11) { - model = scenery_info->pModel[0]; - eSolid *solid = model ? model->GetSolid() : 0; - if (solid && solid->NumPolys > 0x27) { - float lod_scale = solid->Density; - if (lod_scale < 6.0f) { - lod_scale = 6.0f; - } - if ((static_cast(pixel_size_int) / lod_scale) < 8.7f) { - model = scenery_info->pModel[2]; - } - } - } - - if (!model) { - return; - } - - if ((instance->ExcludeFlags & 0x200) != 0) { - SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; - if (draw_info >= scenery_cull_info->pTopDrawInfo) { - return; - } - - scenery_cull_info->pCurrentDrawInfo = draw_info + 1; - draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); - draw_info->pMatrix = 0; - draw_info->SceneryInst = instance; - return; - } - - bMatrix4 *matrix = eFrameMallocMatrix(1); - - if (!matrix) { - return; - } - - instance->GetRotation(matrix); - bFill(&matrix->v3, instance->Position[0], instance->Position[1], instance->Position[2], 1.0f); - - if ((instance->ExcludeFlags & scenery_cull_info->ExcludeFlags & 0x100) != 0) { - matrix->v3.z += EnvMapShadowExtraHeight; - } - if ((scenery_cull_info->ExcludeFlags & 0x800) != 0) { - matrix->v2.z = -matrix->v2.z; - } - - SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; - if (draw_info >= scenery_cull_info->pTopDrawInfo) { - return; - } - - scenery_cull_info->pCurrentDrawInfo = draw_info + 1; - draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); - if ((scenery_cull_info->ExcludeFlags & 0x4000) != 0 && model->GetSolid() && (model->GetSolid()->Flags & 0x80) != 0) { - bMatrix4 windrot; - int offset = static_cast(matrix->v3.x * 60.0f) % 0x168; - CreateWindRotMatrix(scenery_cull_info->pView, &windrot, offset, matrix); - bMulMatrix(matrix, matrix, &windrot); - } - - draw_info->pMatrix = matrix; - draw_info->SceneryInst = instance; - - if (scenery_cull_info->pView == eGetView(1, false) || scenery_cull_info->pView == eGetView(2, false)) { - ePositionMarker *position_marker = 0; - while ((position_marker = model->GetPostionMarker(position_marker)) != 0) { - if (model->GetSolid()) { - unsigned int exclude_view_ids = 2; - if (scenery_cull_info->pView == eGetView(1, false)) { - exclude_view_ids = 1; - } - - eLightFlare *light_flare = eGetNextLightFlareInPool(exclude_view_ids); - if (light_flare) { - bVector4 ps; - ps.x = position_marker->Matrix.v3.x - model->GetSolid()->PivotMatrix.v3.x; - ps.y = position_marker->Matrix.v3.y - model->GetSolid()->PivotMatrix.v3.y; - ps.z = position_marker->Matrix.v3.z - model->GetSolid()->PivotMatrix.v3.z; - ps.w = 1.0f; - eMulVector(&ps, draw_info->pMatrix, &ps); - - if (scenery_cull_info->pView->Precipitation && 0.0f < scenery_cull_info->pView->Precipitation->GetRoadDampness() && - (light_flare->PositionX != ps.x || light_flare->PositionY != ps.y || light_flare->PositionZ != ps.z)) { - light_flare->ReflectPosZ = 999.0f; - } - - light_flare->PositionX = ps.x; - light_flare->PositionY = ps.y; - light_flare->PositionZ = ps.z; - light_flare->Type = position_marker->iParam0 + 14; - if (static_cast(position_marker->iParam0 - 3) < 3) { - bVector2 dr; - light_flare->Flags = 4; - dr.x = ps.x - draw_info->pMatrix->v3.x; - dr.y = ps.y - draw_info->pMatrix->v3.y; - bNormalize(&dr, &dr); - light_flare->DirectionX = dr.x; - light_flare->DirectionY = dr.y; - light_flare->DirectionZ = 0.0f; - } else { - light_flare->Flags = 2; - } - } - } - } - } -} - -void ScenerySectionHeader::TreeCull(SceneryCullInfo *scenery_cull_info) { - const int max_depth = 64; - SceneryTreeNode *node_stack[max_depth]; - unsigned char visibility_state_stack[max_depth]; - SceneryTreeNode **pnode = node_stack + 1; - unsigned char *pvisibility_state = visibility_state_stack + 1; - - node_stack[0] = reinterpret_cast(reinterpret_cast(this)[10]); - visibility_state_stack[0] = 1; - while (pnode != node_stack) { - pnode -= 1; - SceneryTreeNode *node = *pnode; - pvisibility_state -= 1; - unsigned char visibility_state = *pvisibility_state; - if (visibility_state == 1) { - bVector3 bbox_min; - bVector3 bbox_max; - - node->GetBBox(&bbox_min, &bbox_max); - visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); - } - - if (visibility_state != 0) { - for (int child_number = 0; child_number < node->NumChildren; child_number++) { - // TODO - // short child_code = node->Children[child_number]; - short child_code; - if (child_code >= 0) { - DrawAScenery(child_code, scenery_cull_info, visibility_state); - } else { - int scenery_instance_number = child_code * -1; - SceneryTreeNode *child_node; - - child_node = reinterpret_cast(reinterpret_cast(reinterpret_cast(this)[10]) + - static_cast(scenery_instance_number) * 0x24); - - *pnode = child_node; - *pvisibility_state = visibility_state; - pnode += 1; - pvisibility_state += 1; - } - } - } - } -} - -int LoaderScenery(bChunk *chunk) { - if (chunk->GetID() == 0x34108) { - SceneryOverrideInfoTable = reinterpret_cast(chunk->GetData()); - NumSceneryOverrideInfos = static_cast(chunk->Size) / 6; - for (int i = 0; i < NumSceneryOverrideInfos; i++) { - SceneryOverrideInfo *override_info = &SceneryOverrideInfoTable[i]; - override_info->EndianSwap(); - } - BuildSceneryOverrideHashTable(); - return 1; - } - - if (chunk->GetID() == 0x80034100) { - ScenerySectionHeader *section_header = 0; - bChunk *last_chunk = chunk->GetLastChunk(); - - for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { - unsigned int subchunk_id = subchunk->GetID(); - if (subchunk_id == 0x34101) { - section_header = reinterpret_cast(subchunk->GetAlignedData(0x10)); - int *section_header_words = reinterpret_cast(section_header); - if (section_header_words[2] == 0) { - bEndianSwap32(reinterpret_cast(section_header_words) + 0xC); - bEndianSwap32(reinterpret_cast(section_header_words) + 0x10); - bEndianSwap32(reinterpret_cast(section_header_words) + 0x14); - bEndianSwap32(reinterpret_cast(section_header_words) + 0x38); - } - - VisibleSectionUserInfo *user_info = TheVisibleSectionManager.AllocateUserInfo(section_header_words[3]); - user_info->pScenerySectionHeader = section_header; - - if (GetScenerySectionLetter(section_header_words[3]) == 'Z') { - ScenerySectionHeaderList.AddHead(section_header); - } else { - ScenerySectionHeaderList.AddTail(section_header); - } - } else if (subchunk_id == 0x34102) { - int *section_header_words = reinterpret_cast(section_header); - section_header_words[6] = reinterpret_cast(subchunk->GetData()); - section_header_words[7] = static_cast(subchunk->Size) / 0x48; - if (section_header_words[2] == 0) { - for (int i = 0; i < section_header_words[7]; i++) { - bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x40)); - for (int n = 0; n < 4; n++) { - bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x18 + n * 4)); - } - bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x38)); - bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x3C)); - } + ScenerySectionHeaderList.AddTail(section_header); + } + } else if (subchunk_id == 0x34102) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[6] = reinterpret_cast(subchunk->GetData()); + section_header_words[7] = static_cast(subchunk->Size) / 0x48; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[7]; i++) { + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x40)); + for (int n = 0; n < 4; n++) { + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x18 + n * 4)); + } + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x38)); + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x3C)); + } } for (int i = 0; i < section_header_words[7]; i++) { @@ -907,168 +632,515 @@ int LoaderScenery(bChunk *chunk) { bEndianSwap32(&node[i].mNodeName); bEndianSwap32(&node[i].mModelHash); } - - HeirarchyMap[mH->mNameHash] = mH; - for (unsigned int i = 0; i < num_models; i++) { - eModel *model = 0; - if (node[i].mModelHash != 0) { - model = reinterpret_cast(bOMalloc(eModelSlotPool)); - if (model) { - model->NameHash = 0; - model->Solid = 0; - model->Init(node[i].mModelHash); - } - } - node[i].mModel = model; + + HeirarchyMap[mH->mNameHash] = mH; + for (unsigned int i = 0; i < num_models; i++) { + eModel *model = 0; + if (node[i].mModelHash != 0) { + model = reinterpret_cast(bOMalloc(eModelSlotPool)); + if (model) { + model->NameHash = 0; + model->Solid = 0; + model->Init(node[i].mModelHash); + } + } + node[i].mModel = model; + } + } + return 1; + } + + if (chunk->GetID() == 0x80034115) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + if (subchunk->GetID() == 0x34116) { + LightTable = reinterpret_cast(subchunk->GetData()); + } else if (subchunk->GetID() == 0x34117) { + SceneryLightContextTable = reinterpret_cast(subchunk->GetData()); + MaxSceneryLightContexts = (subchunk->Size + 3) >> 2; + } else if (subchunk->GetID() == 0x34118) { + eSceneryLightContext *light_context = reinterpret_cast(subchunk->GetAlignedData(0x10)); + bPlatEndianSwap(&light_context->Type); + bPlatEndianSwap(&light_context->NumLights); + bPlatEndianSwap(&light_context->LightingContextNumber); + light_context->LocalLights = reinterpret_cast(light_context + 1); + for (unsigned int i = 0; i < light_context->NumLights; i++) { + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x00)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x10)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x20)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x30)); + } + SceneryLightContextTable[light_context->LightingContextNumber] = light_context; + } + } + return 1; + } + + return 0; +} + +int UnloaderScenery(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + + if (chunk_id == 0x34108) { + SceneryOverrideInfoTable = 0; + NumSceneryOverrideInfos = 0; + BuildSceneryOverrideHashTable(); + return 1; + } + + if (chunk_id == 0x80034100) { + ScenerySectionHeader *section_header = reinterpret_cast(chunk->GetAlignedData(0x10)); + int *section_header_words = reinterpret_cast(section_header); + TheVisibleSectionManager.GetUserInfo(section_header_words[3])->pScenerySectionHeader = 0; + TheVisibleSectionManager.UnallocateUserInfo(section_header_words[3]); + section_header->Remove(); + + if (!AreChunksBeingMoved()) { + for (int i = 0; i < section_header_words[7]; i++) { + unsigned char *scenery_info = reinterpret_cast(section_header_words[6]) + i * 0x48; + eModel **model_slots = reinterpret_cast(scenery_info + 0x28); + for (int j = 0; j < 4; j++) { + if (AreChunksBeingMoved()) { + break; + } + + if (model_slots[j]) { + eModel *slot_model = model_slots[j]; + for (int k = j + 1; k < 4; k++) { + if (model_slots[k] == slot_model) { + model_slots[k] = 0; + } + } + if (ModelDisconnectionCallback) { + ModelDisconnectionCallback(section_header, i, model_slots[j]); + } + + eModel *model = model_slots[j]; + if (model) { + model->UnInit(); + bFree(eModelSlotPool, model); + } + model_slots[j] = 0; + } + } + } + + if (SectionDisconnectionCallback) { + SectionDisconnectionCallback(section_header); + } + } + + return 1; + } + + if (chunk_id == 0x8003410B) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + ModelHeirarchy *heirarchy = reinterpret_cast(subchunk->GetData()); + ModelHeirarchy::Node *nodes = reinterpret_cast(heirarchy + 1); + unsigned int num_models = heirarchy->mNumNodes; + + for (unsigned int i = 0; i < num_models; i++) { + eModel *model = nodes[i].mModel; + if (model) { + model->UnInit(); + bFree(eModelSlotPool, model); + nodes[i].mModel = 0; + } + } + + ModelHeirarchyMap::iterator it = HeirarchyMap.find(heirarchy->mNameHash); + if (it != HeirarchyMap.end()) { + HeirarchyMap.erase(it); + } + } + return 1; + } + + if (chunk_id == 0x80034115) { + MaxSceneryLightContexts = 0; + LightTable = 0; + SceneryLightContextTable = 0; + return 1; + } + + return 0; +} + +SceneryInfo *FindSceneryInfo(unsigned int name_hash) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = reinterpret_cast(section_header->GetNext())) { + int *section_header_words = reinterpret_cast(section_header); + for (int i = 0; i < section_header_words[7]; i++) { + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + i; + eModel *model = scenery_info->pModel[0]; + if (model && model->NameHash == name_hash) { + return scenery_info; + } + } + } + return 0; +} + +SceneryInstance *FindSceneryInstance(unsigned int name_hash) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = reinterpret_cast(section_header->GetNext())) { + int *section_header_words = reinterpret_cast(section_header); + for (int i = 0; i < section_header_words[9]; i++) { + SceneryInstance *instance = reinterpret_cast(section_header_words[8]) + i; + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + instance->SceneryInfoNumber; + eModel *model = scenery_info->pModel[0]; + if (model && model->NameHash == name_hash) { + return instance; + } + } + } + return 0; +} + +void RenderVisibleSectionBoundary(VisibleSectionBoundary *boundary, eView *view) { + if (boundary->NumPoints <= 0) { + return; + } + + float perimeter; + { + int n; + + for (n = 0; n < boundary->GetNumPoints(); n++) { + bVector2 *v1 = boundary->GetPoint(n); + bVector2 *v2 = boundary->GetPoint((n + 1) % boundary->GetNumPoints()); + float x = v1->x - v2->x; + float y = v1->y - v2->y; + perimeter = bSqrt(x * x + y * y); + } + } + + bVector3 position; + TopologyCoordinate topology_coordinate; + float pos = static_cast((static_cast(WorldTimer.GetSeconds() * 262144.0f) & 0xffff)) * 6.103515625e-05f; + int point_number; + + for (point_number = 0; point_number < boundary->GetNumPoints(); point_number++) { + bVector2 normal = *boundary->GetPoint((point_number + 1) % boundary->GetNumPoints()) - *boundary->GetPoint(point_number); + float length = bLength(&normal); + + bNormalize(&normal, &normal); + if (pos < length) { + do { + bScaleAdd(reinterpret_cast(&position), boundary->GetPoint(point_number), &normal, pos); + + if (topology_coordinate.HasTopology(reinterpret_cast(&position))) { + position.z = 9999.0f; + position.z = topology_coordinate.GetElevation(&position, 0, 0, 0); + int pixel_size = view->GetPixelSize(&position, 1.0f); + if (pixel_size > 0) { + unsigned char *matrix_memory = CurrentBufferPos; + unsigned char *next_buffer_pos = matrix_memory + sizeof(bMatrix4); + if (next_buffer_pos >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += sizeof(bMatrix4); + matrix_memory = 0; + } else { + CurrentBufferPos = next_buffer_pos; + } + + if (matrix_memory) { + bMatrix4 *matrix = reinterpret_cast(matrix_memory); + bIdentity(matrix); + bCopy(&matrix->v3, &position, 1.0f); + reinterpret_cast(view)->Render(pVisibleZoneBoundaryModel, matrix, 0, 0, 0); + } + } + } + + pos += 4.0f; + } while (pos < length); + } + + pos -= length; + } +} + +void RenderVisibleZones(eView *view) { + if (ShowSectionBoarder != 0 && pVisibleZoneBoundaryModel != 0) { + DrivableScenerySection *drivable_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(view->GetCamera()->GetPosition())); + if (drivable_section) { + RenderVisibleSectionBoundary(drivable_section->pBoundary, view); + } + } +} + +void InitVisibleZones() { + if (pVisibleZoneBoundaryModel == 0) { + eModel *model = reinterpret_cast(bOMalloc(eModelSlotPool)); + unsigned int name_hash = bStringHash("MARKER_BOUNDARY"); + model->NameHash = 0; + model->Solid = 0; + model->Init(name_hash); + pVisibleZoneBoundaryModel = model; + } +} + +void CloseVisibleZones() { + eModel *model = pVisibleZoneBoundaryModel; + if (pVisibleZoneBoundaryModel) { + pVisibleZoneBoundaryModel->UnInit(); + bFree(eModelSlotPool, model); + } + pVisibleZoneBoundaryModel = 0; + if (SeeulatorToolActive) { + int data = 0; + bFunkCallASync("Seeulator", 4, &data, 4); + bFunkCallASync("Seeulator", 5, &data, 4); + bFunkCallASync("Seeulator", 6, &data, 4); + } +} + +int IsInTable(short *section_numbers, int num_sections, int section_number) { + for (int i = 0; i < num_sections; i++) { + if (section_numbers[i] == section_number) { + return i; + } + } + return -1; +} + +int ToggleIsInTable(short *section_numbers, int num_sections, int max_sections, int section_number) { + int section_index = IsInTable(section_numbers, num_sections, section_number); + if (section_index >= 0) { + section_numbers[section_index] = -1; + return num_sections; + } + + section_numbers[num_sections % max_sections] = static_cast(section_number); + return num_sections + 1; +} + +void ScenerySectionHeader::DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state) { + int *section_header_words = reinterpret_cast(this); + SceneryInstance *instance = GetSceneryInstance(scenery_instance_number); + tPrecullerInfo *preculler_info = GetPrecullerInfo(instance->PrecullerInfoIndex); + int preculler_section_number = scenery_cull_info->PrecullerSectionNumber; + if (preculler_section_number >= 0) { + int byte_number = preculler_section_number >> 3; + int bit_number = preculler_section_number & 7; + unsigned char visibility_bits = preculler_info->GetBits()[byte_number]; + int visibility_mask = 1 << bit_number; + if ((visibility_bits & visibility_mask) != 0) { + return; + } + } + + unsigned char instance_exclude_flags = instance->ExcludeFlags; + short scenery_info_number = instance->SceneryInfoNumber; + if (((instance_exclude_flags ^ 0x60) & scenery_cull_info->ExcludeFlags) != 0) { + return; + } + int pixel_size_int; + SceneryInfo *scenery_info = reinterpret_cast(GetSceneryInfo_Scenery(section_header_words, scenery_info_number)); + + if (visibility_state == EVISIBLESTATE_PARTIAL) { + bVector3 bbox_min; + bVector3 bbox_max; + instance->GetBBox(&bbox_min, &bbox_max); + visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); + if (visibility_state == EVISIBLESTATE_NOT) { + return; + } + } + + float radius = scenery_info->Radius + 6.0f; + pixel_size_int = InlinedViewGetPixelSize(scenery_cull_info, instance->GetPosition(), radius); + + if (pixel_size_int < 2) { + return; + } + unsigned int instance_flags = instance->ExcludeFlags; + if ((instance_flags & 0x2000000) != 0) { + pixel_size_int += 10; + } + + eModel *model = 0; + if ((scenery_cull_info->ExcludeFlags & 0x1800) != 0) { + if ((instance_flags & 0x80) != 0) { + if (pixel_size_int > 0x1F) { + model = scenery_info->pModel[2]; + } + } else { + if (pixel_size_int > 0x1F) { + if ((instance_flags & 0x1000100) != 0) { + model = scenery_info->pModel[0]; + } else { + model = scenery_info->pModel[3]; + } + } + } + } else if ((scenery_cull_info->ExcludeFlags & 0x20) != 0) { + if (pixel_size_int > 0x1F) { + model = scenery_info->pModel[2]; + } + } else if (eGetCurrentViewMode() > EVIEWMODE_ONE_RVM) { + if (pixel_size_int > 0x16) { + model = scenery_info->pModel[2]; + } + } else if (pixel_size_int > 0x11) { + model = scenery_info->pModel[0]; + eSolid *solid = model ? model->GetSolid() : 0; + if (solid && solid->NumPolys > 0x27) { + float lod_scale = solid->Density; + if (lod_scale < 6.0f) { + lod_scale = 6.0f; + } + if ((static_cast(pixel_size_int) / lod_scale) < 8.7f) { + model = scenery_info->pModel[2]; } } - return 1; } - if (chunk->GetID() == 0x80034115) { - bChunk *last_chunk = chunk->GetLastChunk(); - for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { - if (subchunk->GetID() == 0x34116) { - LightTable = reinterpret_cast(subchunk->GetData()); - } else if (subchunk->GetID() == 0x34117) { - SceneryLightContextTable = reinterpret_cast(subchunk->GetData()); - MaxSceneryLightContexts = (subchunk->Size + 3) >> 2; - } else if (subchunk->GetID() == 0x34118) { - eSceneryLightContext *light_context = reinterpret_cast(subchunk->GetAlignedData(0x10)); - bPlatEndianSwap(&light_context->Type); - bPlatEndianSwap(&light_context->NumLights); - bPlatEndianSwap(&light_context->LightingContextNumber); - light_context->LocalLights = reinterpret_cast(light_context + 1); - for (unsigned int i = 0; i < light_context->NumLights; i++) { - bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x00)); - bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x10)); - bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x20)); - bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x30)); - } - SceneryLightContextTable[light_context->LightingContextNumber] = light_context; - } + if (!model) { + return; + } + + if ((instance->ExcludeFlags & 0x200) != 0) { + SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; + if (draw_info >= scenery_cull_info->pTopDrawInfo) { + return; } - return 1; + + scenery_cull_info->pCurrentDrawInfo = draw_info + 1; + draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); + draw_info->pMatrix = 0; + draw_info->SceneryInst = instance; + return; } - return 0; -} + bMatrix4 *matrix = eFrameMallocMatrix(1); -ScenerySectionHeader *GetScenerySectionHeader(int section_number) { - VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); - if (!user_info) { - return 0; + if (!matrix) { + return; } - return user_info->pScenerySectionHeader; -} -int IsInTable(short *section_numbers, int num_sections, int section_number) { - for (int i = 0; i < num_sections; i++) { - if (section_numbers[i] == section_number) { - return i; - } + instance->GetRotation(matrix); + bFill(&matrix->v3, instance->Position[0], instance->Position[1], instance->Position[2], 1.0f); + + if ((instance->ExcludeFlags & scenery_cull_info->ExcludeFlags & 0x100) != 0) { + matrix->v3.z += EnvMapShadowExtraHeight; + } + if ((scenery_cull_info->ExcludeFlags & 0x800) != 0) { + matrix->v2.z = -matrix->v2.z; } - return -1; -} -int ToggleIsInTable(short *section_numbers, int num_sections, int max_sections, int section_number) { - int section_index = IsInTable(section_numbers, num_sections, section_number); - if (section_index >= 0) { - section_numbers[section_index] = -1; - return num_sections; + SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; + if (draw_info >= scenery_cull_info->pTopDrawInfo) { + return; } - section_numbers[num_sections % max_sections] = static_cast(section_number); - return num_sections + 1; -} + scenery_cull_info->pCurrentDrawInfo = draw_info + 1; + draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); + if ((scenery_cull_info->ExcludeFlags & 0x4000) != 0 && model->GetSolid() && (model->GetSolid()->Flags & 0x80) != 0) { + bMatrix4 windrot; + int offset = static_cast(matrix->v3.x * 60.0f) % 0x168; + CreateWindRotMatrix(scenery_cull_info->pView, &windrot, offset, matrix); + bMulMatrix(matrix, matrix, &windrot); + } -int UnloaderScenery(bChunk *chunk) { - unsigned int chunk_id = chunk->GetID(); + draw_info->pMatrix = matrix; + draw_info->SceneryInst = instance; - if (chunk_id == 0x34108) { - SceneryOverrideInfoTable = 0; - NumSceneryOverrideInfos = 0; - BuildSceneryOverrideHashTable(); - return 1; - } + if (scenery_cull_info->pView == eGetView(1, false) || scenery_cull_info->pView == eGetView(2, false)) { + ePositionMarker *position_marker = 0; + while ((position_marker = model->GetPostionMarker(position_marker)) != 0) { + if (model->GetSolid()) { + unsigned int exclude_view_ids = 2; + if (scenery_cull_info->pView == eGetView(1, false)) { + exclude_view_ids = 1; + } - if (chunk_id == 0x80034100) { - ScenerySectionHeader *section_header = reinterpret_cast(chunk->GetAlignedData(0x10)); - int *section_header_words = reinterpret_cast(section_header); - TheVisibleSectionManager.GetUserInfo(section_header_words[3])->pScenerySectionHeader = 0; - TheVisibleSectionManager.UnallocateUserInfo(section_header_words[3]); - section_header->Remove(); + eLightFlare *light_flare = eGetNextLightFlareInPool(exclude_view_ids); + if (light_flare) { + bVector4 ps; + ps.x = position_marker->Matrix.v3.x - model->GetSolid()->PivotMatrix.v3.x; + ps.y = position_marker->Matrix.v3.y - model->GetSolid()->PivotMatrix.v3.y; + ps.z = position_marker->Matrix.v3.z - model->GetSolid()->PivotMatrix.v3.z; + ps.w = 1.0f; + eMulVector(&ps, draw_info->pMatrix, &ps); - if (!AreChunksBeingMoved()) { - for (int i = 0; i < section_header_words[7]; i++) { - unsigned char *scenery_info = reinterpret_cast(section_header_words[6]) + i * 0x48; - eModel **model_slots = reinterpret_cast(scenery_info + 0x28); - for (int j = 0; j < 4; j++) { - if (AreChunksBeingMoved()) { - break; + if (scenery_cull_info->pView->Precipitation && 0.0f < scenery_cull_info->pView->Precipitation->GetRoadDampness() && + (light_flare->PositionX != ps.x || light_flare->PositionY != ps.y || light_flare->PositionZ != ps.z)) { + light_flare->ReflectPosZ = 999.0f; } - if (model_slots[j]) { - eModel *slot_model = model_slots[j]; - for (int k = j + 1; k < 4; k++) { - if (model_slots[k] == slot_model) { - model_slots[k] = 0; - } - } - if (ModelDisconnectionCallback) { - ModelDisconnectionCallback(section_header, i, model_slots[j]); - } - - eModel *model = model_slots[j]; - if (model) { - model->UnInit(); - bFree(eModelSlotPool, model); - } - model_slots[j] = 0; + light_flare->PositionX = ps.x; + light_flare->PositionY = ps.y; + light_flare->PositionZ = ps.z; + light_flare->Type = position_marker->iParam0 + 14; + if (static_cast(position_marker->iParam0 - 3) < 3) { + bVector2 dr; + light_flare->Flags = 4; + dr.x = ps.x - draw_info->pMatrix->v3.x; + dr.y = ps.y - draw_info->pMatrix->v3.y; + bNormalize(&dr, &dr); + light_flare->DirectionX = dr.x; + light_flare->DirectionY = dr.y; + light_flare->DirectionZ = 0.0f; + } else { + light_flare->Flags = 2; } } } + } + } +} - if (SectionDisconnectionCallback) { - SectionDisconnectionCallback(section_header); - } +void ScenerySectionHeader::TreeCull(SceneryCullInfo *scenery_cull_info) { + const int max_depth = 64; + SceneryTreeNode *node_stack[max_depth]; + unsigned char visibility_state_stack[max_depth]; + SceneryTreeNode **pnode = node_stack + 1; + unsigned char *pvisibility_state = visibility_state_stack + 1; + + node_stack[0] = reinterpret_cast(reinterpret_cast(this)[10]); + visibility_state_stack[0] = 1; + while (pnode != node_stack) { + pnode -= 1; + SceneryTreeNode *node = *pnode; + pvisibility_state -= 1; + unsigned char visibility_state = *pvisibility_state; + if (visibility_state == 1) { + bVector3 bbox_min; + bVector3 bbox_max; + + node->GetBBox(&bbox_min, &bbox_max); + visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); } - return 1; - } + if (visibility_state != 0) { + for (int child_number = 0; child_number < node->NumChildren; child_number++) { + // TODO + // short child_code = node->Children[child_number]; + short child_code; + if (child_code >= 0) { + DrawAScenery(child_code, scenery_cull_info, visibility_state); + } else { + int scenery_instance_number = child_code * -1; + SceneryTreeNode *child_node; - if (chunk_id == 0x8003410B) { - bChunk *last_chunk = chunk->GetLastChunk(); - for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { - ModelHeirarchy *heirarchy = reinterpret_cast(subchunk->GetData()); - ModelHeirarchy::Node *nodes = reinterpret_cast(heirarchy + 1); - unsigned int num_models = heirarchy->mNumNodes; + child_node = reinterpret_cast(reinterpret_cast(reinterpret_cast(this)[10]) + + static_cast(scenery_instance_number) * 0x24); - for (unsigned int i = 0; i < num_models; i++) { - eModel *model = nodes[i].mModel; - if (model) { - model->UnInit(); - bFree(eModelSlotPool, model); - nodes[i].mModel = 0; + *pnode = child_node; + *pvisibility_state = visibility_state; + pnode += 1; + pvisibility_state += 1; } } - - ModelHeirarchyMap::iterator it = HeirarchyMap.find(heirarchy->mNameHash); - if (it != HeirarchyMap.end()) { - HeirarchyMap.erase(it); - } } - return 1; - } - - if (chunk_id == 0x80034115) { - MaxSceneryLightContexts = 0; - LightTable = 0; - SceneryLightContextTable = 0; - return 1; } - - return 0; } int GrandSceneryCullInfo::WhatSectionsShouldWeDraw(short *sections_to_draw, int max_sections_to_draw, SceneryCullInfo *scenery_cull_info) { @@ -1224,16 +1296,6 @@ void GrandSceneryCullInfo::DoCulling() { } } -void RenderVisibleZones(eView *view) { - if (ShowSectionBoarder != 0 && pVisibleZoneBoundaryModel != 0) { - DrivableScenerySection *drivable_section = - TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(view->GetCamera()->GetPosition())); - if (drivable_section) { - RenderVisibleSectionBoundary(drivable_section->pBoundary, view); - } - } -} - void GrandSceneryCullInfo::StuffScenery(eView *view, int stuff_flags) { unsigned int base_flags = 0; unsigned int forbidden_flags = 0; @@ -1329,3 +1391,5 @@ void GrandSceneryCullInfo::StuffScenery(eView *view, int stuff_flags) { return; } } +void ServicePreculler() {} + diff --git a/src/Speed/Indep/Src/World/ScreenEffects.cpp b/src/Speed/Indep/Src/World/ScreenEffects.cpp index d7576847c..83dfb7133 100644 --- a/src/Speed/Indep/Src/World/ScreenEffects.cpp +++ b/src/Speed/Indep/Src/World/ScreenEffects.cpp @@ -55,7 +55,7 @@ class WWorldPosTopologyShim : public WWorldPos { } }; -void InitScreenEFX() {} +void InitScreenEFX(); enum TunnelBloomDataIndex { kTunnelPoint0X = 0, @@ -89,15 +89,6 @@ ScreenEffectDB::ScreenEffectDB() { } InitScreenEFX(); } -void TickSFX() { - if (TheGameFlowManager.IsInGame()) { - if (ticS_27592 != eFrameCounter - 1) { - UpdateAllScreenEFX(); - } - ticS_27592 = eFrameCounter; - } -} - void ScreenEffectDB::Update(float deltatime) { SE_time += deltatime; @@ -113,25 +104,6 @@ void ScreenEffectDB::Update(float deltatime) { } } -float TopologyCoordinate::GetElevation(const bVector3 *position, enum TerrainType *type, bVector3 *normal, bool *point_valid) { - UMath::Vector3 bond_pos; - UMath::Vector4 dummy_normal; - - (void)type; - (void)normal; - - bConvertToBond(bond_pos, *position); - WWorldPosTopologyShim world_pos(0.025f); - world_pos.Update(bond_pos, dummy_normal, true, 0, true); - if (point_valid) { - *point_valid = world_pos.OnValidFace(); - } - if (world_pos.OnValidFace()) { - return world_pos.HeightAtPoint(bond_pos); - } - return position->z; -} - void ScreenEffectDB::AddScreenEffect(ScreenEffectType type, float intensity, float r, float g, float b) { ScreenEffectDef info; @@ -177,77 +149,79 @@ void ScreenEffectDB::AddScreenEffect(ScreenEffectType type, ScreenEffectDef *inf } } +void ScreenEffectDB::AddPaletteEffect(ScreenEffectPalette palette) { + AddPaletteEffect(&SE_PaletteFile[palette]); +} + void ScreenEffectDB::AddPaletteEffect(ScreenEffectPaletteDef *palette) { for (int i = 0; i < palette->NumEffects; i++) { AddScreenEffect(palette->SE_type[i], &palette->SE_Def[i], 1, palette->SE_Controller[i]); } } -void ScreenEffectDB::AddPaletteEffect(ScreenEffectPalette palette) { - AddPaletteEffect(&SE_PaletteFile[palette]); +void InitScreenEFX() {} + +void TickSFX() { + if (TheGameFlowManager.IsInGame()) { + if (ticS_27592 != eFrameCounter - 1) { + UpdateAllScreenEFX(); + } + ticS_27592 = eFrameCounter; + } } -void RenderVisibleSectionBoundary(VisibleSectionBoundary *boundary, eView *view) { - if (boundary->NumPoints <= 0) { - return; +void UpdateAllScreenEFX() { + for (int i = 1; i <= 2; i++) { + eView *view = eGetView(i, false); + if (view->IsActive()) { + eGetView(i, false)->ScreenEffects->Update(0.033333335f); + if (debugflash != 0) { + debugflash = 0; + eGetView(i, false)->ScreenEffects->AddPaletteEffect(EFX_CAMERA_FLASH); + } + } } +} - float perimeter; - { - int n; +void FlushAccumulationBuffer() { + AccumulationBufferNeedsFlush = 1; +} - for (n = 0; n < boundary->GetNumPoints(); n++) { - bVector2 *v1 = boundary->GetPoint(n); - bVector2 *v2 = boundary->GetPoint((n + 1) % boundary->GetNumPoints()); - float x = v1->x - v2->x; - float y = v1->y - v2->y; - perimeter = bSqrt(x * x + y * y); - } +void AccumulationBufferFlushed() { + AccumulationBufferNeedsFlush = 0; +} + +unsigned int QueryFlushAccumulationBuffer() { + return AccumulationBufferNeedsFlush; +} +void DoTinting(eView *view) { + ScreenEffectDef SE_def; + unsigned int r; + unsigned int g; + unsigned int b; + float intense; + + if (IsRainDisabled()) { + return; } - bVector3 position; - TopologyCoordinate topology_coordinate; - float pos = static_cast((static_cast(WorldTimer.GetSeconds() * 262144.0f) & 0xffff)) * 6.103515625e-05f; - int point_number; - - for (point_number = 0; point_number < boundary->GetNumPoints(); point_number++) { - bVector2 normal = *boundary->GetPoint((point_number + 1) % boundary->GetNumPoints()) - *boundary->GetPoint(point_number); - float length = bLength(&normal); - - bNormalize(&normal, &normal); - if (pos < length) { - do { - bScaleAdd(reinterpret_cast(&position), boundary->GetPoint(point_number), &normal, pos); - - if (topology_coordinate.HasTopology(reinterpret_cast(&position))) { - position.z = 9999.0f; - position.z = topology_coordinate.GetElevation(&position, 0, 0, 0); - int pixel_size = view->GetPixelSize(&position, 1.0f); - if (pixel_size > 0) { - unsigned char *matrix_memory = CurrentBufferPos; - unsigned char *next_buffer_pos = matrix_memory + sizeof(bMatrix4); - if (next_buffer_pos >= CurrentBufferEnd) { - FrameMallocFailed = 1; - FrameMallocFailAmount += sizeof(bMatrix4); - matrix_memory = 0; - } else { - CurrentBufferPos = next_buffer_pos; - } - - if (matrix_memory) { - bMatrix4 *matrix = reinterpret_cast(matrix_memory); - bIdentity(matrix); - bCopy(&matrix->v3, &position, 1.0f); - reinterpret_cast(view)->Render(pVisibleZoneBoundaryModel, matrix, 0, 0, 0); - } - } - } - - pos += 4.0f; - } while (pos < length); - } + if (view->Precipitation) { + intense = view->Precipitation->GetCloudIntensity(); + } else { + intense = 0.0f; + } - pos -= length; + if (0.0f < intense) { + if (view->Precipitation) { + view->Precipitation->GetPrecipFogColour(&r, &g, &b); + } + SE_def.r = static_cast(r); + SE_def.g = static_cast(g); + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.intensity = intense; + SE_def.b = static_cast(b); + view->ScreenEffects->AddScreenEffect(SE_TINT, &SE_def, 1, SEC_FRAME); } } @@ -439,59 +413,3 @@ void DoTunnelBloom(eView *view) { } } } - -void DoTinting(eView *view) { - ScreenEffectDef SE_def; - unsigned int r; - unsigned int g; - unsigned int b; - float intense; - - if (IsRainDisabled()) { - return; - } - - if (view->Precipitation) { - intense = view->Precipitation->GetCloudIntensity(); - } else { - intense = 0.0f; - } - - if (0.0f < intense) { - if (view->Precipitation) { - view->Precipitation->GetPrecipFogColour(&r, &g, &b); - } - SE_def.r = static_cast(r); - SE_def.g = static_cast(g); - SE_def.a = 128.0f; - SE_def.UpdateFnc = 0; - SE_def.intensity = intense; - SE_def.b = static_cast(b); - view->ScreenEffects->AddScreenEffect(SE_TINT, &SE_def, 1, SEC_FRAME); - } -} - -void UpdateAllScreenEFX() { - for (int i = 1; i <= 2; i++) { - eView *view = eGetView(i, false); - if (view->IsActive()) { - eGetView(i, false)->ScreenEffects->Update(0.033333335f); - if (debugflash != 0) { - debugflash = 0; - eGetView(i, false)->ScreenEffects->AddPaletteEffect(EFX_CAMERA_FLASH); - } - } - } -} - -void FlushAccumulationBuffer() { - AccumulationBufferNeedsFlush = 1; -} - -void AccumulationBufferFlushed() { - AccumulationBufferNeedsFlush = 0; -} - -unsigned int QueryFlushAccumulationBuffer() { - return AccumulationBufferNeedsFlush; -} diff --git a/src/Speed/Indep/Src/World/Track.cpp b/src/Speed/Indep/Src/World/Track.cpp index e50245b9c..1daf337db 100644 --- a/src/Speed/Indep/Src/World/Track.cpp +++ b/src/Speed/Indep/Src/World/Track.cpp @@ -1,10 +1,29 @@ #include "Track.hpp" +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" #include "Speed/Indep/bWare/Inc/bWare.hpp" #include "Speed/Indep/bWare/Inc/bChunk.hpp" void bEndianSwap32(void *value); +static inline UMath::Vector3 &bConvertToBond_Track(UMath::Vector3 &dest, const bVector3 &v) { + bConvertToBond(*reinterpret_cast(&dest), v); + return dest; +} + +class WWorldPosTopologyShim_Track : public WWorldPos { + public: + WWorldPosTopologyShim_Track(float yOffset) + : WWorldPos(yOffset) { + fFace.fPt0 = UMath::Vector3::kZero; + fFace.fPt1 = UMath::Vector3::kZero; + fFace.fPt2 = UMath::Vector3::kZero; + fFace.fSurface.fSurface = 0; + fFace.fSurface.fFlags = 0; + } +}; + enum TerrainType { TERRAIN_TYPE_NONE = 0, TERRAIN_TYPE_ROAD = 1, @@ -64,8 +83,6 @@ static char *TrackOBBTable = 0; static int NumTrackOBBs = 0; bChunkLoader bChunkLoaderTrackOBB(0x34191, LoaderTrackOBB, UnloaderTrackOBB); -void EstablishRemoteCaffeineConnection() {} - int GetNumTrackOBBs() { return NumTrackOBBs; } @@ -99,6 +116,27 @@ int UnloaderTrackOBB(bChunk *chunk) { return 1; } +void EstablishRemoteCaffeineConnection() {} + +float TopologyCoordinate::GetElevation(const bVector3 *position, enum TerrainType *type, bVector3 *normal, bool *point_valid) { + UMath::Vector3 bond_pos; + UMath::Vector4 dummy_normal; + + (void)type; + (void)normal; + + bConvertToBond_Track(bond_pos, *position); + WWorldPosTopologyShim_Track world_pos(0.025f); + world_pos.Update(bond_pos, dummy_normal, true, 0, true); + if (point_valid) { + *point_valid = world_pos.OnValidFace(); + } + if (world_pos.OnValidFace()) { + return world_pos.HeightAtPoint(bond_pos); + } + return position->z; +} + int TopologyCoordinate::HasTopology(const bVector2 *position) { float test_elevation; bVector3 test_position(position->x, position->y, 99999.1015625f); diff --git a/src/Speed/Indep/Src/World/TrackInfo.cpp b/src/Speed/Indep/Src/World/TrackInfo.cpp index 3cd9ee1de..e0f8e01d9 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.cpp +++ b/src/Speed/Indep/Src/World/TrackInfo.cpp @@ -8,6 +8,17 @@ static unsigned int NumTrackInfo = 0; TrackInfo *LoadedTrackInfo = 0; bChunkLoader bChunkLoaderTrackInfo(0x34201, TrackInfo::LoaderTrackInfo, TrackInfo::UnloaderTrackInfo); +TrackInfo *TrackInfo::GetTrackInfo(int track_number) { + for (int n = 0; n < static_cast(NumTrackInfo); n++) { + TrackInfo *info = &TrackInfoTable[n]; + if (info->TrackNumber == track_number) { + return info; + } + } + + return 0; +} + int TrackInfo::LoaderTrackInfo(bChunk *chunk) { int i; int j; @@ -84,17 +95,6 @@ int TrackInfo::LoaderTrackInfo(bChunk *chunk) { return 0; } -TrackInfo *TrackInfo::GetTrackInfo(int track_number) { - for (int n = 0; n < static_cast(NumTrackInfo); n++) { - TrackInfo *info = &TrackInfoTable[n]; - if (info->TrackNumber == track_number) { - return info; - } - } - - return 0; -} - int TrackInfo::UnloaderTrackInfo(bChunk *chunk) { if (chunk->GetID() == 0x34201) { TrackInfoTable = 0; diff --git a/src/Speed/Indep/Src/World/TrackPath.cpp b/src/Speed/Indep/Src/World/TrackPath.cpp index 4f5ea2aff..a4a348e97 100644 --- a/src/Speed/Indep/Src/World/TrackPath.cpp +++ b/src/Speed/Indep/Src/World/TrackPath.cpp @@ -24,6 +24,28 @@ TrackPathManager TheTrackPathManager; bChunkLoader bChunkLoaderTrackPath(0x80034147, LoaderTrackPath, UnloaderTrackPath); bChunkLoader bChunkLoaderTrackPathBarriers(0x3414D, LoaderTrackPath, UnloaderTrackPath); +bool DoLinesIntersect(const bVector2 &line1_start, const bVector2 &line1_end, const bVector2 &line2_start, const bVector2 &line2_end) { + float dy1 = line1_end.y - line1_start.y; + float dx2 = line2_end.x - line2_start.x; + float dx1 = line1_end.x - line1_start.x; + float dy2 = line2_end.y - line2_start.y; + float den = dx1 * dy2 - dy1 * dx2; + + if (den != 0.0f) { + float dx3 = line1_start.x - line2_start.x; + float dy3 = line1_start.y - line2_start.y; + float r = (dy3 * dx2 - dx3 * dy2) / den; + if (0.0f <= r && r <= 1.0f) { + float s = (dy3 * dx1 - dx3 * dy1) / den; + if (0.0f <= s && s <= 1.0f) { + return true; + } + } + } + + return false; +} + void TrackPathManager::Clear() { NumZones = 0; SizeofZones = 0; @@ -222,6 +244,15 @@ bool TrackPathZone::IsPointInside(const bVector2 *point) { return bIsPointInPoly(point, Points, NumPoints); } +void TrackPathInitRemoteCaffeineConnection() {} + +int LoaderTrackPath(bChunk *chunk) { + return TheTrackPathManager.Loader(chunk); +} + +int UnloaderTrackPath(bChunk *chunk) { + return TheTrackPathManager.Unloader(chunk); +} float TrackPathZone::GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a, bVector2 *segment_point_b) { int Closest0 = -1; int Closest1 = -1; @@ -256,12 +287,3 @@ float TrackPathZone::GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a return d0; } -void TrackPathInitRemoteCaffeineConnection() {} - -int LoaderTrackPath(bChunk *chunk) { - return TheTrackPathManager.Loader(chunk); -} - -int UnloaderTrackPath(bChunk *chunk) { - return TheTrackPathManager.Unloader(chunk); -} diff --git a/src/Speed/Indep/Src/World/TrackPositionMarker.cpp b/src/Speed/Indep/Src/World/TrackPositionMarker.cpp index 8f3f86a66..91a7cb89f 100644 --- a/src/Speed/Indep/Src/World/TrackPositionMarker.cpp +++ b/src/Speed/Indep/Src/World/TrackPositionMarker.cpp @@ -9,15 +9,6 @@ bChunkLoader bChunkLoaderTrackPositionMarkers(0x34146, LoaderTrackPositionMarker static void NotifyTrackMarkersChanged() {} -void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *, unsigned int), unsigned int tag) { - for (TrackPositionMarker *marker = TrackPositionMarkerList.GetHead(); marker != TrackPositionMarkerList.EndOfList(); - marker = marker->GetNext()) { - if (!callback(marker, tag)) { - break; - } - } -} - int LoaderTrackPositionMarkers(bChunk *chunk) { if (chunk->GetID() == 0x34146) { TrackPositionMarker *marker_table = reinterpret_cast(chunk->GetAlignedData(0x10)); @@ -69,6 +60,15 @@ int GetNumTrackPositionMarkers(int track_number, unsigned int name_hash) { return num_markers; } +void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *, unsigned int), unsigned int tag) { + for (TrackPositionMarker *marker = TrackPositionMarkerList.GetHead(); marker != TrackPositionMarkerList.EndOfList(); + marker = marker->GetNext()) { + if (!callback(marker, tag)) { + break; + } + } +} + TrackPositionMarker *GetTrackPositionMarker(int track_number, unsigned int name_hash, int index) { int num_markers = 0; diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index d5dba217b..ca047b45a 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -127,28 +127,6 @@ static inline void eAllowDuplicateSolids(bool enable) { } } -bool DoLinesIntersect(const bVector2 &line1_start, const bVector2 &line1_end, const bVector2 &line2_start, const bVector2 &line2_end) { - float dy1 = line1_end.y - line1_start.y; - float dx2 = line2_end.x - line2_start.x; - float dx1 = line1_end.x - line1_start.x; - float dy2 = line2_end.y - line2_start.y; - float den = dx1 * dy2 - dy1 * dx2; - - if (den != 0.0f) { - float dx3 = line1_start.x - line2_start.x; - float dy3 = line1_start.y - line2_start.y; - float r = (dy3 * dx2 - dx3 * dy2) / den; - if (0.0f <= r && r <= 1.0f) { - float s = (dy3 * dx1 - dx3 * dy1) / den; - if (0.0f <= s && s <= 1.0f) { - return true; - } - } - } - - return false; -} - inline bool TrackStreamingBarrier::Intersects(const bVector2 *pointa, const bVector2 *pointb) { return DoLinesIntersect(Points[0], Points[1], *pointa, *pointb); } @@ -342,6 +320,51 @@ void *TSMemoryPool::Malloc(int size, const char *debug_name, bool best_fit, bool return reinterpret_cast(address); } +void TSMemoryPool::Free(void *memory) { + int address = reinterpret_cast(memory); + Updated = true; + + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->Address == address) { + int size; + TSMemoryNode *prev_node = node->GetPrev(); + + node->DebugName[0] = 0; + size = node->Size; + node->Allocated = false; + if (prev_node != NodeList.EndOfList() && prev_node->IsFree()) { + node->Address = address - prev_node->Size; + node->Size = size + prev_node->Size; + NodeList.Remove(prev_node); + RemoveNode(prev_node); + } + + TSMemoryNode *next_node = node->GetNext(); + if (next_node != NodeList.EndOfList() && next_node->IsFree()) { + node->Size += next_node->Size; + NodeList.Remove(next_node); + RemoveNode(next_node); + } + + AmountFree += size; + if (node->Size > LargestFree) { + LargestFree = node->Size; + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceFreePacket packet; + bMemoryTraceFreePacket *packet_ptr = &packet; + memset(packet_ptr, 0, sizeof(*packet_ptr)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = address; + packet.Size = size; + bFunkCallASync("CODEINE", 0x1b, packet_ptr, sizeof(*packet_ptr)); + } + return; + } + } +} + inline void *TSMemoryPool::OverrideMalloc(void *pool, int size, const char *debug_text, int debug_line, int allocation_params) { register int user_alignment_offset; (void)debug_line; @@ -399,51 +422,6 @@ int TSMemoryPool::GetLargestFreeBlock() { return LargestFree; } -void TSMemoryPool::Free(void *memory) { - int address = reinterpret_cast(memory); - Updated = true; - - for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { - if (node->Address == address) { - int size; - TSMemoryNode *prev_node = node->GetPrev(); - - node->DebugName[0] = 0; - size = node->Size; - node->Allocated = false; - if (prev_node != NodeList.EndOfList() && prev_node->IsFree()) { - node->Address = address - prev_node->Size; - node->Size = size + prev_node->Size; - NodeList.Remove(prev_node); - RemoveNode(prev_node); - } - - TSMemoryNode *next_node = node->GetNext(); - if (next_node != NodeList.EndOfList() && next_node->IsFree()) { - node->Size += next_node->Size; - NodeList.Remove(next_node); - RemoveNode(next_node); - } - - AmountFree += size; - if (node->Size > LargestFree) { - LargestFree = node->Size; - } - - if (TracingEnabled && bMemoryTracing) { - bMemoryTraceFreePacket packet; - bMemoryTraceFreePacket *packet_ptr = &packet; - memset(packet_ptr, 0, sizeof(*packet_ptr)); - packet.PoolID = reinterpret_cast(this); - packet.MemoryAddress = address; - packet.Size = size; - bFunkCallASync("CODEINE", 0x1b, packet_ptr, sizeof(*packet_ptr)); - } - return; - } - } -} - TSMemoryNode *TSMemoryPool::GetNextNode(bool start_from_top, TSMemoryNode *node) { if (start_from_top) { if (node) { @@ -483,6 +461,10 @@ TSMemoryNode *TSMemoryPool::GetNextAllocatedNode(bool start_from_top, TSMemoryNo return 0; } +unsigned int TSMemoryPool::GetPoolChecksum() { + return 0; +} + inline TSMemoryNode *TSMemoryPool::GetFirstAllocatedNode(bool start_from_top) { return GetNextAllocatedNode(start_from_top, 0); } @@ -495,190 +477,232 @@ void TSMemoryPool::DebugPrint() { } } -unsigned int TSMemoryPool::GetPoolChecksum() { - return 0; +int LoaderTrackStreamer(bChunk *chunk) { + return TheTrackStreamer.Loader(chunk); } -void *TrackStreamer::AllocateMemory(TrackStreamingSection *section, int allocation_params) { - void *buf = bMalloc(section->Size, section->SectionName, 0, allocation_params | 0x2007); - if (!buf) { - bBreak(); - } - return buf; +int UnloaderTrackStreamer(bChunk *chunk) { + return TheTrackStreamer.Unloader(chunk); } -bool TrackStreamer::WillUnloadBlock(TrackStreamingSection *section) { - unsigned int frame = section->UnactivatedFrameCount; - if ((frame != 0) && (frame == eFrameCounter)) { - if (LastWaitUntilRenderingDoneFrameCount != frame) { - return true; - } - } - return false; +void RefreshTrackStreamer() { + TheTrackStreamer.RefreshLoading(); } -void TrackStreamer::UnloadSection(TrackStreamingSection *section) { - if (section->Status == TrackStreamingSection::ACTIVATED) { - UnactivateSection(section); - } +TrackStreamer::TrackStreamer() { + pTrackStreamingSections = 0; + NumTrackStreamingSections = 0; + pDiscBundleSections = 0; + pLastDiscBundleSection = 0; + NumSectionsLoaded = 0; + NumSectionsLoading = 0; + NumSectionsActivated = 0; + NumSectionsOutOfMemory = 0; + NumSectionsMoved = 0; + bMemSet(StreamFilenames, 0, sizeof(StreamFilenames)); + SplitScreen = false; + PermFileLoading = false; + PermFilename = 0; + PermFileChunks = 0; + PermFileSize = 0; + NumBarriers = 0; + pBarriers = 0; + NumCurrentStreamingSections = 0; + NumHibernatingSections = 0; + CurrentZoneNeedsRefreshing = false; + ZoneSwitchingDisabled = false; + LastWaitUntilRenderingDoneFrameCount = 0; + LastPrintedFrameCount = 0; + SkipNextHandleLoad = false; - if (section->Status == TrackStreamingSection::LOADED) { - if (WillUnloadBlock(section)) { - WaitForFrameBufferSwapDisabled = 1; - eWaitUntilRenderingDone(); - WaitForFrameBufferSwapDisabled = 0; - LastWaitUntilRenderingDoneFrameCount = eFrameCounter; - } + ClearCurrentZones(); + ClearStreamingPositions(); - section->UnactivatedFrameCount = 0; - bFree(section->pMemory); - section->LoadedTime = 0; - section->pMemory = 0; - section->Status = TrackStreamingSection::UNLOADED; - NumSectionsLoaded -= 1; - } + pMemoryPoolMem = 0; + MemoryPoolSize = 0; + UserMemoryAllocationSize = 0; + pMemoryPool = 0; + + CurrentVisibleSectionTable.Init(CurrentVisibleSectionTableMem.Bits, 0xAF0); + CurrentVisibleSectionTable.ClearTable(); + bMemSet(KeepSectionTable, 0, sizeof(KeepSectionTable)); + pCallback = 0; + CallbackParam = 0; + MakeSpaceInPoolCallback = 0; + MakeSpaceInPoolCallbackParam = 0; + MakeSpaceInPoolSize = 0; } -int TrackStreamer::UnloadLeastRecentlyUsedSection() { - TrackStreamingSection *best_section = 0; +int TrackStreamer::Loader(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + if (chunk_id == 0x34110) { + pTrackStreamingSections = reinterpret_cast(chunk->GetData()); + NumTrackStreamingSections = chunk->Size / sizeof(TrackStreamingSection); + for (int i = 0; i < NumTrackStreamingSections; i++) { + TrackStreamingSection *section = &pTrackStreamingSections[i]; + bEndianSwap16(§ion->SectionNumber); + bEndianSwap32(§ion->FileType); + bEndianSwap32(§ion->Status); + bEndianSwap32(§ion->FileOffset); + bEndianSwap32(§ion->Size); + bEndianSwap32(§ion->CompressedSize); + bEndianSwap32(§ion->PermSize); + bEndianSwap32(§ion->SectionPriority); + bPlatEndianSwap(§ion->Centre); + bEndianSwap32(§ion->Radius); + bEndianSwap32(§ion->Checksum); + } - for (int n = 0; n < NumTrackStreamingSections; n++) { - TrackStreamingSection *section = &pTrackStreamingSections[n]; - if (section->Status == TrackStreamingSection::LOADED && !section->CurrentlyVisible && - (!best_section || section->LastNeededTimestamp < best_section->LastNeededTimestamp)) { - best_section = section; + for (int i = 0; i < NumHibernatingSections; i++) { + TrackStreamingSection *src = &HibernatingSections[i]; + TrackStreamingSection *section = FindSection(src->SectionNumber); + bMemCpy(section, src, sizeof(TrackStreamingSection)); + NumSectionsLoaded += 1; + ActivateSection(section); + int current_streaming_section = NumCurrentStreamingSections; + CurrentStreamingSections[current_streaming_section] = section; + NumCurrentStreamingSections = current_streaming_section + 1; } - } - if (!best_section) { + NumHibernatingSections = 0; + return 1; + } else if (chunk_id == 0x34113) { + pDiscBundleSections = reinterpret_cast(chunk->GetData()); + pLastDiscBundleSection = reinterpret_cast(reinterpret_cast(pDiscBundleSections) + chunk->Size); + for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; + disc_bundle = reinterpret_cast(reinterpret_cast(disc_bundle) + + (disc_bundle->NumMembers * sizeof(DiscBundleSectionMember) + 0x14))) { + bEndianSwap32(&disc_bundle->FileOffset); + bEndianSwap32(&disc_bundle->FileSize); + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + bEndianSwap16(&member->SectionNumber); + bEndianSwap16(&member->FileOffset); + member->pSection = FindSection(member->SectionNumber); + } + } + return 1; + } else if (chunk_id == 0x34111) { + pInfo = reinterpret_cast(chunk->GetData()); + for (int i = 0; i < 2; i++) { + bEndianSwap32(i + pInfo->FileSize); + } + return 1; + } else if (chunk_id == 0x34112) { + pBarriers = reinterpret_cast(chunk->GetData()); + NumBarriers = chunk->Size / sizeof(TrackStreamingBarrier); + for (int i = 0; i < NumBarriers; i++) { + TrackStreamingBarrier *barrier = &pBarriers[i]; + bPlatEndianSwap(&barrier->Points[0]); + bPlatEndianSwap(&barrier->Points[1]); + } + return 1; + } else { return 0; } - - UnloadSection(best_section); - return best_section->LoadedSize; } -void TrackStreamer::JettisonSection(TrackStreamingSection *section) { - AmountJettisoned += section->Size; - JettisonedSections[NumJettisonedSections] = section; - NumJettisonedSections += 1; - - if (section->Status == TrackStreamingSection::ACTIVATED) { - UnactivateSection(section); - } - if (section->Status == TrackStreamingSection::LOADED) { - UnloadSection(section); +int TrackStreamer::Unloader(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + if (chunk_id == 0x34110) { + UnloadEverything(); + pTrackStreamingSections = 0; + NumTrackStreamingSections = 0; + return 1; } - section->CurrentlyVisible = false; + if (chunk_id == 0x34113) { + pDiscBundleSections = 0; + pLastDiscBundleSection = 0; + return 1; + } - int index = 0; - while (CurrentStreamingSections[index] != section) { - index += 1; + if (chunk_id == 0x34111) { + pInfo = 0; + return 1; } - while (index < NumCurrentStreamingSections - 1) { - CurrentStreamingSections[index] = CurrentStreamingSections[index + 1]; - index += 1; + if (chunk_id == 0x34112) { + pBarriers = 0; + NumBarriers = 0; + return 1; } - NumCurrentStreamingSections -= 1; + return 0; } -bool TrackStreamer::JettisonLeastImportantSection() { - TrackStreamingSection *best_section = ChooseSectionToJettison(); - if (best_section) { - JettisonSection(best_section); - return true; +void TrackStreamer::ClearCurrentZones() { + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->AmountLoaded = 0; + position_entry->CurrentZone = 0; + position_entry->BeginLoadingTime = 0.0f; + position_entry->BeginLoadingPosition.x = 0.0f; + position_entry->BeginLoadingPosition.y = 0.0f; + position_entry->NumSectionsToLoad = 0; + position_entry->NumSectionsLoaded = 0; + position_entry->AmountToLoad = 0; } - return false; -} - -int TrackStreamer::AllocateSectionMemory(int *ptotal_needing_allocation) { - ProfileNode profile_node("TODO", 0); - int out_of_memory_size = 0; - int total_needing_allocation = 0; - int num_sections_allocated = 0; - - if (LoadingPhase == ALLOCATING_REGULAR_SECTIONS && pDiscBundleSections < pLastDiscBundleSection) { - for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; - disc_bundle = disc_bundle->GetMemoryImageNext()) { - int i = 0; - if (disc_bundle->NumMembers > 0) { - do { - DiscBundleSectionMember *member = &disc_bundle->Members[i]; - TrackStreamingSection *section = member->pSection; - if (!section->CurrentlyVisible || section->Status != TrackStreamingSection::UNLOADED) { - break; - } - i += 1; - } while (i < disc_bundle->NumMembers); - } - - if (i == disc_bundle->NumMembers) { - if (disc_bundle->FileSize <= pMemoryPool->GetLargestFreeBlock()) { - unsigned char *pmemory = - static_cast(pMemoryPool->Malloc(disc_bundle->FileSize, disc_bundle->SectionName, true, false, 0)); - pMemoryPool->Free(pmemory); - for (i = 0; i < disc_bundle->NumMembers; i++) { - DiscBundleSectionMember *member = &disc_bundle->Members[i]; - TrackStreamingSection *section = member->pSection; - void *realloc_mem = pmemory + member->FileOffset * 0x80; + CurrentZoneFarLoad = true; + StartLoadingTime = 0.0f; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + CurrentZoneNonReplayLoad = false; + LoadingPhase = LOADING_IDLE; + LoadingBacklog = 0.0f; + CurrentZoneName[0] = 0; + NumJettisonedSections = 0; + MemorySafetyMargin = 0; + AmountJettisoned = 0; + bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); + RemoveCurrentStreamingSections(); +} - num_sections_allocated += 1; - section->pDiscBundle = disc_bundle; - section->Status = TrackStreamingSection::ALLOCATED; - section->pMemory = realloc_mem; - pMemoryPool->Malloc(section->Size, disc_bundle->SectionName, false, false, reinterpret_cast(realloc_mem)); - } +void TrackStreamer::InitMemoryPool(int size) { + MemoryPoolSize = size; +#ifdef MILESTONE_OPT + pMemoryPoolMem = bMalloc(size, "Track Streaming", 0, 0x2000); +#else + pMemoryPoolMem = bMalloc(size, 0x2000); +#endif + pMemoryPool = new TSMemoryPool(reinterpret_cast(pMemoryPoolMem), MemoryPoolSize, "Track Streaming", 7); +} - total_needing_allocation += disc_bundle->FileSize; - } - } - } +int TrackStreamer::GetMemoryPoolSize() { + if (pMemoryPool->IsUpdated()) { + UserMemoryAllocationSize = CountUserAllocations(0); } + return MemoryPoolSize - UserMemoryAllocationSize; +} - for (int i = 0; i < NumCurrentStreamingSections; i++) { - TrackStreamingSection *section = CurrentStreamingSections[i]; - if (section->Status != TrackStreamingSection::UNLOADED) { - continue; - } +int TrackStreamer::CountUserAllocations(const char **pfragmented_user_allocation) { + int num_fragmented_user_allocations; - if (section->SectionNumber != GetScenerySectionNumber('Y', 0) && section->SectionNumber != GetScenerySectionNumber('X', 0) && - section->SectionNumber != GetScenerySectionNumber('W', 0) && section->SectionNumber != GetScenerySectionNumber('U', 0) && - section->SectionNumber != GetScenerySectionNumber('Z', 0)) { - if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS) { - if (!IsTextureSection(section->SectionNumber)) { - continue; - } - } + if (pfragmented_user_allocation) { + *pfragmented_user_allocation = 0; + } - if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS) { - if (!IsLibrarySection(section->SectionNumber)) { - continue; - } + num_fragmented_user_allocations = 0; + int user_allocation_size = 0; + bool start_from_top = false; + TSMemoryNode *node = pMemoryPool->GetFirstAllocatedNode(start_from_top); + while (node) { + TrackStreamingSection *section = FindSectionByAddress(node->Address); + if (!section) { + user_allocation_size += node->Size; + if (pMemoryPool->GetNextFreeNode(start_from_top, node) && pMemoryPool->GetNextFreeNode(!start_from_top, node) && + pfragmented_user_allocation) { + *pfragmented_user_allocation = node->DebugName; + num_fragmented_user_allocations += 1; } } - total_needing_allocation += section->Size; - if (bLargestMalloc(7) < section->Size) { - out_of_memory_size += section->Size; - NumSectionsOutOfMemory += 1; - } else { - num_sections_allocated += 1; - section->pMemory = AllocateMemory(section, 0x80); - section->Status = TrackStreamingSection::ALLOCATED; - if (num_sections_allocated > 99999) { - CurrentZoneAllocatedButIncomplete = true; - return out_of_memory_size; - } - } + node = pMemoryPool->GetNextAllocatedNode(start_from_top, node); } - CurrentZoneAllocatedButIncomplete = false; - *ptotal_needing_allocation = total_needing_allocation; - return out_of_memory_size; + (void)num_fragmented_user_allocations; + return user_allocation_size; } TrackStreamingSection *TrackStreamer::FindSection(int section_number) { @@ -717,1072 +741,1016 @@ TrackStreamingSection *TrackStreamer::FindSectionByAddress(int address) { return 0; } -int TrackStreamer::BuildHoleMovements(HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, - int max_amount_to_move) { - ProfileNode profile_node("TODO", 0); - int ticks = bGetTicker(); - unsigned int checksum = pMemoryPool->GetPoolChecksum(); - bool failed; - int num_movements; - int amount_moved; - int total_needing_allocation; +int TrackStreamer::GetCombinedSectionNumber(int section_number) { + bool use_combined_section = false; + if ((static_cast(section_number / 100 - 1) & 0xFF) < 0x14) { + int subsection_number = section_number % 100; + use_combined_section = subsection_number > 0 && subsection_number < ScenerySectionLODOffset; + } - pMemoryPool->EnableTracing(false); - total_needing_allocation = -1; - failed = false; - num_movements = 0; - amount_moved = 0; - while (true) { - if (largest_free >= 0) { - if (pMemoryPool->GetLargestFreeBlock() >= largest_free) { - break; - } - } else { - int out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); - FreeSectionMemory(); - if (out_of_memory_size == 0) { - break; + if (use_combined_section) { + int combined_section_number = section_number + ScenerySectionLODOffset; + TrackStreamingSection *section = FindSection(section_number); + if (!section) { + section = FindSection(combined_section_number); + if (section) { + return combined_section_number; } } + } - if (filler_method != 0) { - if (pMemoryPool->GetAmountFree() == pMemoryPool->GetLargestFreeBlock()) { - break; - } - } + return section_number; +} - if (num_movements == max_movements) { - break; - } +void TrackStreamer::InitRegion(const char *region_stream_filename, bool split_screen) { + bool flush_hibernating_sections = false; - HoleMovement *movement = &hole_movements[num_movements]; - movement->Address = 0; + if (SplitScreen != split_screen) { + SplitScreen = split_screen; + flush_hibernating_sections = true; + } + if (!bStrEqual(StreamFilenames[1], region_stream_filename)) { + flush_hibernating_sections = true; + bStrCpy(StreamFilenames[1], region_stream_filename); + } + if (flush_hibernating_sections) { + FlushHibernatingSections(); + } + if (PermFileLoading) { + BlockWhileQueuedFileBusy(); + } - if (filler_method == 2 || filler_method == 0 || filler_method == 3) { - bool start_from_top = filler_method == 3; - TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); - TSMemoryNode *node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); - if (filler_method == 0 && !node) { - break; - } + ClearCurrentZones(); + ClearStreamingPositions(); - if (node && free_node) { - movement->Size = node->Size; - movement->Address = node->Address; - movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); - if (filler_method == 0 && !FindSectionByAddress(movement->Address)) { - break; - } - } - } else if (filler_method == 4 || filler_method == 5) { - bool start_from_top = filler_method == 5; - TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); - int best_hole_size = 0; - bool first = true; - - for (TSMemoryNode *next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); next_node; - next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, next_node)) { - TSMemoryNode *next_free = pMemoryPool->GetNextFreeNode(start_from_top, next_node); - if (!next_free) { - continue; - } - if (first || next_node->Size <= free_node->Size) { - TSMemoryNode *node1 = pMemoryPool->GetNextNode(!start_from_top, next_node); - TSMemoryNode *node2 = pMemoryPool->GetNextNode(start_from_top, next_node); - int hole_size = next_node->Size; - if (node1 && node1->IsFree()) { - hole_size += node1->Size; - } - if (node2 && node2->IsFree()) { - hole_size += node2->Size; - } - if (hole_size > best_hole_size) { - best_hole_size = hole_size; - movement->Size = next_node->Size; - movement->Address = next_node->Address; - movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); - first = false; - } - } - } - } else if (filler_method == 1) { - bool done = false; - bool found_one = false; - bool found_big_enough = false; - TSMemoryNode *largest_allocated = 0; - int current_best = 0; - int current_best_middle_memory = 0x3E8000; - int best_address = 0; - bool first_pass = true; - TSMemoryNode *top_free_top = 0; + { + int position_number = 0; + do { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - do { - if (first_pass) { - first_pass = false; - top_free_top = pMemoryPool->GetFirstNode(true); - } else { - top_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); - } + position_entry->AudioBlockingPosition.x = 0.0f; + position_entry->PredictedZone = 0; + position_entry->PredictedZoneValidTime = 0; + position_entry->AudioReading = false; + position_entry->AudioReadingTime = 0.0f; + position_entry->AudioReadingPosition.x = 0.0f; + position_entry->AudioReadingPosition.y = 0.0f; + position_entry->AudioBlocking = false; + position_entry->AudioBlockingTime = 0.0f; + position_entry->AudioBlockingPosition.y = 0.0f; + position_number += 1; + } while (position_number < 2); + } - if (!top_free_top) { - done = true; - } else { - int top_free_memory = top_free_top->Size; - TSMemoryNode *bottom_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); - if (!bottom_free_top) { - done = true; - } else { - TSMemoryNode *top_allocated = pMemoryPool->GetNextNode(true, top_free_top); - pMemoryPool->GetNextNode(false, bottom_free_top); + int n = 0; + while (n < NumTrackStreamingSections) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + int boundary_section_number = GetBoundarySectionNumber(static_cast(section->SectionNumber), bGetPlatformName()); + VisibleSectionBoundary *boundary = TheVisibleSectionManager.FindBoundary(boundary_section_number); - int middle_allocated_memory = top_allocated->Size; - int total_free_memory = top_free_memory + bottom_free_top->Size; - int size_checking[32]; - int i = 0; - do { - size_checking[i] = 0; - i += 1; - } while (i < 32); + section->pBoundary = boundary; + n += 1; + } - size_checking[0] = top_allocated->Size; + EmptyCaffeineLayers(); +} - TSMemoryNode *largest_allocated_here = top_allocated; - TSMemoryNode *cursor = top_allocated; - int found_nodes = 1; - while ((cursor = pMemoryPool->GetNextNode(true, top_allocated)) != bottom_free_top && top_allocated && found_nodes < 32) { - top_allocated = pMemoryPool->GetNextNode(true, top_allocated); - if (top_allocated) { - size_checking[found_nodes] = top_allocated->Size; - middle_allocated_memory += top_allocated->Size; - found_nodes += 1; - if (top_allocated->Size > largest_allocated_here->Size) { - largest_allocated_here = top_allocated; - } - } - } +void TrackStreamer::HibernateStreamingSections() { + int sections_to_hibernate[5]; + int n; + int section_number; + TrackStreamingSection *section; + TrackStreamingSection *hibernating_section; - int free_gap = total_free_memory - middle_allocated_memory; - if ((!found_big_enough && current_best < free_gap) || - (found_big_enough && total_free_memory + middle_allocated_memory >= total_needing_allocation && - middle_allocated_memory < current_best_middle_memory)) { - std::sort(size_checking, size_checking + found_nodes); - int evaluated_best_address = 0; - bool largest_flag = false; - int nodes_to_move = 0; - int position = 0; + (void)sections_to_hibernate; + (void)n; + (void)section_number; + (void)section; + (void)hibernating_section; + return; +} - TSMemoryNode *evaluated_top_free = pMemoryPool->GetFirstFreeNode(true); - while (found_nodes > nodes_to_move && evaluated_top_free) { - bool skip_flag = false; - int target_index = found_nodes - nodes_to_move - 1; - for (int i = 0; i < found_nodes; i++) { - if (size_checking[target_index] == position) { - skip_flag = true; - } - } - if (evaluated_top_free == top_free_top || evaluated_top_free == bottom_free_top) { - skip_flag = true; - } - if (!skip_flag && evaluated_top_free->Size >= size_checking[target_index]) { - size_checking[target_index] = position; - nodes_to_move += 1; - if (!largest_flag) { - evaluated_best_address = evaluated_top_free->Address; - largest_flag = true; - } - evaluated_top_free = pMemoryPool->GetNextFreeNode(true, 0); - } - evaluated_top_free = pMemoryPool->GetNextFreeNode(true, evaluated_top_free); - position += 1; - } +void TrackStreamer::FlushHibernatingSections() { + for (int n = 0; n < NumHibernatingSections; n++) { + TrackStreamingSection *section = &HibernatingSections[n]; + bFree(section->pMemory); + } + NumHibernatingSections = 0; +} - if (nodes_to_move >= found_nodes) { - current_best = free_gap; - best_address = evaluated_best_address; - found_one = true; - largest_allocated = largest_allocated_here; - if (total_free_memory + middle_allocated_memory >= total_needing_allocation) { - found_big_enough = true; - current_best_middle_memory = middle_allocated_memory; - } - } - } - } - } - } while (!done); +void *TrackStreamer::AllocateMemory(TrackStreamingSection *section, int allocation_params) { + void *buf = bMalloc(section->Size, section->SectionName, 0, allocation_params | 0x2007); + if (!buf) { + bBreak(); + } + return buf; +} - if (found_one && largest_allocated && FindSectionByAddress(largest_allocated->Address)) { - movement->Size = largest_allocated->Size; - movement->Address = largest_allocated->Address; - movement->NewAddress = best_address; - } +void TrackStreamer::LoadDiscBundle(DiscBundleSection *disc_bundle) { + void *memory = 0; + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + if (i == 0) { + memory = section->pMemory; } + section->Status = TrackStreamingSection::LOADING; + } - if (movement->Address == 0) { - failed = true; - break; - } + NumSectionsLoading += 1; + AddQueuedFile(memory, StreamFilenames[1], disc_bundle->FileOffset, disc_bundle->FileSize, DiscBundleLoadedCallback, + reinterpret_cast(disc_bundle), 0); +} - num_movements += 1; - movement->Checksum = pMemoryPool->GetPoolChecksum(); - pMemoryPool->Free(reinterpret_cast(movement->Address)); - pMemoryPool->Malloc(movement->Size, "HoleMovement", false, false, movement->NewAddress); - amount_moved += movement->Size; - if (max_amount_to_move < amount_moved) { - failed = true; - break; - } - } +void TrackStreamer::DiscBundleLoadedCallback(int param, int error_status) { + (void)error_status; + TheTrackStreamer.DiscBundleLoadedCallback(reinterpret_cast(param)); +} - for (int n = num_movements - 1; n >= 0; n--) { - HoleMovement *movement = &hole_movements[n]; - pMemoryPool->Free(reinterpret_cast(movement->NewAddress)); - TrackStreamingSection *section = FindSectionByAddress(movement->Address); - char *debug_name; - if (section) { - debug_name = section->SectionName; - } else { - debug_name = "UndoHoleMovement"; - } - pMemoryPool->Malloc(movement->Size, debug_name, false, false, movement->Address); +void TrackStreamer::DiscBundleLoadedCallback(DiscBundleSection *disc_bundle) { + NumSectionsLoading += -1 + disc_bundle->NumMembers; + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + section->pDiscBundle = 0; + SectionLoadedCallback(section); } +} - pMemoryPool->EnableTracing(true); - if (pamount_moved) { - *pamount_moved = amount_moved; - } - if (failed) { - return -1; +void TrackStreamer::LoadSection(TrackStreamingSection *section) { + NumSectionsLoading += 1; + section->Status = TrackStreamingSection::LOADING; + + if (section->CompressedSize == section->Size) { + AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, + reinterpret_cast(section), 0); + } else { + QueuedFileParams params; + params.BlockSize = 0x7ffffff; + params.Priority = QueuedFileDefaultPriority; + params.Compressed = false; + params.Compressed = true; + params.UncompressedSize = section->Size; + AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, + reinterpret_cast(section), ¶ms); } - return num_movements; } -TrackStreamer::TrackStreamer() { - pTrackStreamingSections = 0; - NumTrackStreamingSections = 0; - pDiscBundleSections = 0; - pLastDiscBundleSection = 0; - NumSectionsLoaded = 0; - NumSectionsLoading = 0; - NumSectionsActivated = 0; - NumSectionsOutOfMemory = 0; - NumSectionsMoved = 0; - bMemSet(StreamFilenames, 0, sizeof(StreamFilenames)); - SplitScreen = false; - PermFileLoading = false; - PermFilename = 0; - PermFileChunks = 0; - PermFileSize = 0; - NumBarriers = 0; - pBarriers = 0; - NumCurrentStreamingSections = 0; - NumHibernatingSections = 0; - CurrentZoneNeedsRefreshing = false; - ZoneSwitchingDisabled = false; - LastWaitUntilRenderingDoneFrameCount = 0; - LastPrintedFrameCount = 0; - SkipNextHandleLoad = false; +void TrackStreamer::ActivateSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + int allocation_params = 0x2087; + NumSectionsActivated += 1; + eAllowDuplicateSolids(true); + SetDuplicateTextureWarning(false); - ClearCurrentZones(); - ClearStreamingPositions(); + bChunk *chunks = reinterpret_cast(section->pMemory); + int sizeof_chunks = section->LoadedSize; + LoadTempPermChunks(&chunks, &sizeof_chunks, allocation_params, section->SectionName); - pMemoryPoolMem = 0; - MemoryPoolSize = 0; - UserMemoryAllocationSize = 0; - pMemoryPool = 0; + section->pMemory = chunks; + section->LoadedSize = sizeof_chunks; + section->Status = TrackStreamingSection::ACTIVATED; + section->LoadedTime = 0; + eAllowDuplicateSolids(false); + SetDuplicateTextureWarning(true); +} - CurrentVisibleSectionTable.Init(CurrentVisibleSectionTableMem.Bits, 0xAF0); - CurrentVisibleSectionTable.ClearTable(); - bMemSet(KeepSectionTable, 0, sizeof(KeepSectionTable)); - pCallback = 0; - CallbackParam = 0; - MakeSpaceInPoolCallback = 0; - MakeSpaceInPoolCallbackParam = 0; - MakeSpaceInPoolSize = 0; +void TrackStreamer::UnactivateSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + section->UnactivatedFrameCount = 0; + DisableWaitUntilRenderingDone(); + section->UnactivatedFrameCount = eGetFrameCounter(); + UnloadChunks(reinterpret_cast(section->pMemory), section->LoadedSize, section->SectionName); + EnableWaitUntilRenderingDone(); + NumSectionsActivated -= 1; + section->Status = TrackStreamingSection::LOADED; } -int LoaderTrackStreamer(bChunk *chunk) { - return TheTrackStreamer.Loader(chunk); +bool TrackStreamer::WillUnloadBlock(TrackStreamingSection *section) { + unsigned int frame = section->UnactivatedFrameCount; + if ((frame != 0) && (frame == eFrameCounter)) { + if (LastWaitUntilRenderingDoneFrameCount != frame) { + return true; + } + } + return false; } -int UnloaderTrackStreamer(bChunk *chunk) { - return TheTrackStreamer.Unloader(chunk); +void TrackStreamer::UnloadSection(TrackStreamingSection *section) { + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + + if (section->Status == TrackStreamingSection::LOADED) { + if (WillUnloadBlock(section)) { + WaitForFrameBufferSwapDisabled = 1; + eWaitUntilRenderingDone(); + WaitForFrameBufferSwapDisabled = 0; + LastWaitUntilRenderingDoneFrameCount = eFrameCounter; + } + + section->UnactivatedFrameCount = 0; + bFree(section->pMemory); + section->LoadedTime = 0; + section->pMemory = 0; + section->Status = TrackStreamingSection::UNLOADED; + NumSectionsLoaded -= 1; + } } -void RefreshTrackStreamer() { - TheTrackStreamer.RefreshLoading(); -} +bool TrackStreamer::NeedsGameStateActivation(TrackStreamingSection *section) { + return false; -void TrackStreamer::InitMemoryPool(int size) { - MemoryPoolSize = size; -#ifdef MILESTONE_OPT - pMemoryPoolMem = bMalloc(size, "Track Streaming", 0, 0x2000); -#else - pMemoryPoolMem = bMalloc(size, 0x2000); -#endif - pMemoryPool = new TSMemoryPool(reinterpret_cast(pMemoryPoolMem), MemoryPoolSize, "Track Streaming", 7); + if (IsRegularScenerySection(section->SectionNumber) && IsLODScenerySectionNumber(section->SectionNumber)) { + return true; + } } -int TrackStreamer::GetMemoryPoolSize() { - if (pMemoryPool->IsUpdated()) { - UserMemoryAllocationSize = CountUserAllocations(0); - } - return MemoryPoolSize - UserMemoryAllocationSize; +void TrackStreamer::SectionLoadedCallback(int param, int error_status) { + (void)error_status; + TheTrackStreamer.SectionLoadedCallback(reinterpret_cast(param)); } -int TrackStreamer::CountUserAllocations(const char **pfragmented_user_allocation) { - int num_fragmented_user_allocations; +void TrackStreamer::SectionLoadedCallback(TrackStreamingSection *section) { + section->Status = TrackStreamingSection::LOADED; + section->LoadedSize = section->Size; + EndianSwapChunkHeadersRecursive(reinterpret_cast(section->pMemory), section->Size); + NumSectionsLoading -= 1; + NumSectionsLoaded += 1; + section->LoadedTime = RealTimeFrames; - if (pfragmented_user_allocation) { - *pfragmented_user_allocation = 0; + if (section->CurrentlyVisible && !NeedsGameStateActivation(section)) { + ActivateSection(section); } - num_fragmented_user_allocations = 0; - int user_allocation_size = 0; - bool start_from_top = false; - TSMemoryNode *node = pMemoryPool->GetFirstAllocatedNode(start_from_top); - while (node) { - TrackStreamingSection *section = FindSectionByAddress(node->Address); - if (!section) { - user_allocation_size += node->Size; - if (pMemoryPool->GetNextFreeNode(start_from_top, node) && pMemoryPool->GetNextFreeNode(!start_from_top, node) && - pfragmented_user_allocation) { - *pfragmented_user_allocation = node->DebugName; - num_fragmented_user_allocations += 1; - } + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (((section->CurrentlyVisible >> position_number) & 1) != 0) { + position_entry->NumSectionsLoaded += 1; + position_entry->AmountLoaded += section->Size; } - - node = pMemoryPool->GetNextAllocatedNode(start_from_top, node); } - (void)num_fragmented_user_allocations; - return user_allocation_size; + CalculateLoadingBacklog(); +#ifdef EA_PLATFORM_GAMECUBE + DCStoreRange(section->pMemory, section->LoadedSize); +#endif } -int TrackStreamer::DoHoleFilling(int largest_free) { - ProfileNode profile_node("TODO", 0); - const char *fragmented_user_allocation; - HoleMovement hole_movement_table[128]; +void TrackStreamer::EmptyCaffeineLayers() { + TrackStreamerRemoteCaffeinating = 0; +} - CountUserAllocations(&fragmented_user_allocation); - if (fragmented_user_allocation) { - pMemoryPool->DebugPrint(); - return 0; +void TrackStreamer::SetLoadingPhase(eLoadingPhase phase) { + LoadingPhase = phase; + if (phase == LOADING_IDLE || phase == LOADING_REGULAR_SECTIONS) { + SetQueuedFileMinPriority(0); + } else { + SetQueuedFileMinPriority(QueuedFileDefaultPriority); } +} - int best_method = -1; - int forced_hole_filler_method = ForceHoleFillerMethod; - if (forced_hole_filler_method >= 0) { - int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, forced_hole_filler_method, largest_free, 0, 0x7FFFFFFF); - if (num_hole_movements > 0) { - best_method = forced_hole_filler_method; - } - } else { - int best_amount_moved = 0x7FFFFFFF; - for (int filler_method = 1; filler_method < 6; filler_method++) { - int amount_moved; - int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, filler_method, largest_free, &amount_moved, best_amount_moved); - if (num_hole_movements > 0 && amount_moved < best_amount_moved) { - best_method = filler_method; - best_amount_moved = amount_moved; - } +int TrackStreamer::UnloadLeastRecentlyUsedSection() { + TrackStreamingSection *best_section = 0; + + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::LOADED && !section->CurrentlyVisible && + (!best_section || section->LastNeededTimestamp < best_section->LastNeededTimestamp)) { + best_section = section; } } - if (best_method < 0) { + if (!best_section) { return 0; } - int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, best_method, largest_free, 0, 0x7FFFFFFF); - for (int n = 0; n < num_hole_movements; n++) { - ProfileNode profile_node("TODO", 0); - HoleMovement *movement = &hole_movement_table[n]; - TrackStreamingSection *section = FindSectionByAddress(movement->Address); - if (LastWaitUntilRenderingDoneFrameCount != eGetFrameCounter()) { - int start_ticks = bGetTicker(); - DisableWaitForFrameBufferSwap(); - eWaitUntilRenderingDone(); - LastWaitUntilRenderingDoneFrameCount = eGetFrameCounter(); - EnableWaitForFrameBufferSwap(); - float time = bGetTickerDifference(start_ticks); - (void)time; - } + UnloadSection(best_section); + return best_section->LoadedSize; +} - int start_ticks = bGetTicker(); - void *new_memory = reinterpret_cast(movement->NewAddress); - pMemoryPool->Free(reinterpret_cast(movement->Address)); - pMemoryPool->Malloc(movement->Size, section->SectionName, false, false, movement->NewAddress); - if (section->Status == TrackStreamingSection::ACTIVATED) { - eAllowDuplicateSolids(true); - SetDuplicateTextureWarning(false); - MoveChunks(reinterpret_cast(new_memory), reinterpret_cast(section->pMemory), section->LoadedSize, - section->SectionName); -#ifdef EA_PLATFORM_GAMECUBE - DCStoreRangeNoSync(new_memory, section->LoadedSize); -#endif - eAllowDuplicateSolids(false); - SetDuplicateTextureWarning(true); - } else { - eWaitUntilRenderingDone(); - bOverlappedMemCpy(new_memory, section->pMemory, section->LoadedSize); - } - section->pMemory = new_memory; - float move_time = bGetTickerDifference(start_ticks); - (void)move_time; - NumSectionsMoved += 1; -#ifdef EA_PLATFORM_GAMECUBE - PPCSync(); -#endif +void TrackStreamer::JettisonSection(TrackStreamingSection *section) { + AmountJettisoned += section->Size; + JettisonedSections[NumJettisonedSections] = section; + NumJettisonedSections += 1; + + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + if (section->Status == TrackStreamingSection::LOADED) { + UnloadSection(section); } - return 1; -} + section->CurrentlyVisible = false; -void TrackStreamer::ClearCurrentZones() { - for (int position_number = 0; position_number < 2; position_number++) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - position_entry->AmountLoaded = 0; - position_entry->CurrentZone = 0; - position_entry->BeginLoadingTime = 0.0f; - position_entry->BeginLoadingPosition.x = 0.0f; - position_entry->BeginLoadingPosition.y = 0.0f; - position_entry->NumSectionsToLoad = 0; - position_entry->NumSectionsLoaded = 0; - position_entry->AmountToLoad = 0; + int index = 0; + while (CurrentStreamingSections[index] != section) { + index += 1; } - CurrentZoneFarLoad = true; - StartLoadingTime = 0.0f; - CurrentZoneOutOfMemory = false; - CurrentZoneAllocatedButIncomplete = false; - CurrentZoneNonReplayLoad = false; - LoadingPhase = LOADING_IDLE; - LoadingBacklog = 0.0f; - CurrentZoneName[0] = 0; - NumJettisonedSections = 0; - MemorySafetyMargin = 0; - AmountJettisoned = 0; - bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); - RemoveCurrentStreamingSections(); + while (index < NumCurrentStreamingSections - 1) { + CurrentStreamingSections[index] = CurrentStreamingSections[index + 1]; + index += 1; + } + + NumCurrentStreamingSections -= 1; } -void TrackStreamer::RemoveCurrentStreamingSections() { - for (int i = 0; i < NumCurrentStreamingSections; i++) { - CurrentStreamingSections[i]->CurrentlyVisible = 0; +bool TrackStreamer::JettisonLeastImportantSection() { + TrackStreamingSection *best_section = ChooseSectionToJettison(); + if (best_section) { + JettisonSection(best_section); + return true; } - - NumCurrentStreamingSections = 0; - bBitTableLayout_TrackStreamer *layout = reinterpret_cast(&CurrentVisibleSectionTable); - bMemSet(layout->Bits, 0, layout->NumBits >> 3); + return false; } -void TrackStreamer::AddCurrentStreamingSections(short *section_numbers, int num_sections, int position_number) { - int i = 0; - if (i < num_sections) { - StreamingPositionEntry *streaming_position = &StreamingPositionEntries[position_number]; - unsigned int position_bit = 1 << position_number; - do { - short §ion_number = section_numbers[i]; - CurrentVisibleSectionTable.Set(section_number); - if (SplitScreen) { - section_number = static_cast(Get2PlayerSectionNumber(section_number)); - } +TrackStreamingSection *TrackStreamer::ChooseSectionToJettison() { + TrackStreamingSection *best_section = 0; + int best_discard_priority = 0; + static int last_jettison_print; + bool print_jettison_this_frame = false; - TrackStreamingSection *section = FindSection(section_number); - if (!section) { - continue; + last_jettison_print = RealLoopCounter; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + int discard_priority = 0; + TrackStreamingSection *section = CurrentStreamingSections[i]; + + if (IsTextureSection(section->SectionNumber) || IsLibrarySection(section->SectionNumber)) { + discard_priority = 2; + if (section->SectionNumber == GetScenerySectionNumber('Y', 0) || section->SectionNumber == GetScenerySectionNumber('W', 0) || + section->SectionNumber == GetScenerySectionNumber('X', 0)) { + discard_priority = 1; + } else if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS && IsTextureSection(section->SectionNumber) && + section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { + discard_priority = 10000; + } else if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS && IsLibrarySection(section->SectionNumber) && + section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { + discard_priority = 10000; } - - section->LastNeededTimestamp = RealTimeFrames; - if (!section->CurrentlyVisible) { - CurrentStreamingSections[NumCurrentStreamingSections++] = section; + } else if (IsRegularScenerySection(section->SectionNumber)) { + int loading_priority = GetLoadingPriority(section, &StreamingPositionEntries[0], true); + if (SplitScreen) { + int loading_priority2 = GetLoadingPriority(section, &StreamingPositionEntries[1], true); + if (loading_priority2 < loading_priority) { + loading_priority = loading_priority2; + } } + discard_priority = loading_priority * 10 + 100; + } - if ((((static_cast(section->CurrentlyVisible) >> position_number) ^ 1U) & 1) != 0) { - section->CurrentlyVisible |= static_cast(position_bit); - if (section->Status < TrackStreamingSection::LOADED) { - streaming_position->NumSectionsToLoad += 1; - streaming_position->AmountToLoad += section->Size; - } + if (discard_priority != 0) { + if (static_cast(section->Status - TrackStreamingSection::LOADED) > 1) { + discard_priority += 1; } - i += 1; - } while (i < num_sections); + } + if (discard_priority > best_discard_priority) { + best_section = section; + best_discard_priority = discard_priority; + } } -} -void TrackStreamer::DetermineStreamingSections() { - const int max_sections_to_load = 0x180; - short sections_to_load[384]; - int num_sections_to_load = 3; - unsigned short section_number; + return best_section; +} - RemoveCurrentStreamingSections(); - sections_to_load[0] = GetScenerySectionNumber_TrackStreamer('Y', 0); - sections_to_load[1] = GetScenerySectionNumber_TrackStreamer('X', 0); - sections_to_load[2] = GetScenerySectionNumber_TrackStreamer('Z', 0); +void TrackStreamer::UnJettisonSections() { + for (int n = 0; n < NumJettisonedSections; n++) { + TrackStreamingSection *section = JettisonedSections[n]; + CurrentStreamingSections[NumCurrentStreamingSections++] = section; + section->CurrentlyVisible = true; + } + NumJettisonedSections = 0; + AmountJettisoned = 0; +} - { - short *sections_to_load_ptr = sections_to_load; - if (SeeulatorToolActive && ScenerySectionToBlink != 0) { - num_sections_to_load = 4; - sections_to_load_ptr[3] = static_cast(ScenerySectionToBlink); - } +int TrackStreamer::BuildHoleMovements(HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, + int max_amount_to_move) { + ProfileNode profile_node("TODO", 0); + int ticks = bGetTicker(); + unsigned int checksum = pMemoryPool->GetPoolChecksum(); + bool failed; + int num_movements; + int amount_moved; + int total_needing_allocation; - for (int n = 0; n < 4; n++) { - section_number = KeepSectionTable[n]; - if (section_number != 0) { - sections_to_load_ptr[num_sections_to_load] = section_number; - num_sections_to_load += 1; + pMemoryPool->EnableTracing(false); + total_needing_allocation = -1; + failed = false; + num_movements = 0; + amount_moved = 0; + while (true) { + if (largest_free >= 0) { + if (pMemoryPool->GetLargestFreeBlock() >= largest_free) { + break; + } + } else { + int out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + FreeSectionMemory(); + if (out_of_memory_size == 0) { + break; } } - AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 0); - AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 1); - int position_number = 0; - do { - { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - if (position_entry->CurrentZone > 0) { - { - LoadingSection *loading_section = TheVisibleSectionManager.FindLoadingSection(position_entry->CurrentZone); - if (!loading_section) { - { - DrivableScenerySection *drivable_section = TheVisibleSectionManager.FindDrivableSection(position_entry->CurrentZone); - num_sections_to_load = 0; - for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { - { - int section_number = drivable_section->GetVisibleSection(i); - sections_to_load_ptr[num_sections_to_load] = section_number; - num_sections_to_load += 1; - } - } - } - } else { - num_sections_to_load = - TheVisibleSectionManager.GetSectionsToLoad(loading_section, sections_to_load_ptr, max_sections_to_load); - } - } - - AddCurrentStreamingSections(sections_to_load, num_sections_to_load, position_number); - } + if (filler_method != 0) { + if (pMemoryPool->GetAmountFree() == pMemoryPool->GetLargestFreeBlock()) { + break; } - position_number += 1; - } while (position_number < 2); - } -} + } -void TrackStreamer::InitRegion(const char *region_stream_filename, bool split_screen) { - bool flush_hibernating_sections = false; + if (num_movements == max_movements) { + break; + } - if (SplitScreen != split_screen) { - SplitScreen = split_screen; - flush_hibernating_sections = true; - } - if (!bStrEqual(StreamFilenames[1], region_stream_filename)) { - flush_hibernating_sections = true; - bStrCpy(StreamFilenames[1], region_stream_filename); - } - if (flush_hibernating_sections) { - FlushHibernatingSections(); - } - if (PermFileLoading) { - BlockWhileQueuedFileBusy(); - } + HoleMovement *movement = &hole_movements[num_movements]; + movement->Address = 0; - ClearCurrentZones(); - ClearStreamingPositions(); + if (filler_method == 2 || filler_method == 0 || filler_method == 3) { + bool start_from_top = filler_method == 3; + TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); + TSMemoryNode *node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); + if (filler_method == 0 && !node) { + break; + } - { - int position_number = 0; - do { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (node && free_node) { + movement->Size = node->Size; + movement->Address = node->Address; + movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); + if (filler_method == 0 && !FindSectionByAddress(movement->Address)) { + break; + } + } + } else if (filler_method == 4 || filler_method == 5) { + bool start_from_top = filler_method == 5; + TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); + int best_hole_size = 0; + bool first = true; - position_entry->AudioBlockingPosition.x = 0.0f; - position_entry->PredictedZone = 0; - position_entry->PredictedZoneValidTime = 0; - position_entry->AudioReading = false; - position_entry->AudioReadingTime = 0.0f; - position_entry->AudioReadingPosition.x = 0.0f; - position_entry->AudioReadingPosition.y = 0.0f; - position_entry->AudioBlocking = false; - position_entry->AudioBlockingTime = 0.0f; - position_entry->AudioBlockingPosition.y = 0.0f; - position_number += 1; - } while (position_number < 2); - } + for (TSMemoryNode *next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); next_node; + next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, next_node)) { + TSMemoryNode *next_free = pMemoryPool->GetNextFreeNode(start_from_top, next_node); + if (!next_free) { + continue; + } + if (first || next_node->Size <= free_node->Size) { + TSMemoryNode *node1 = pMemoryPool->GetNextNode(!start_from_top, next_node); + TSMemoryNode *node2 = pMemoryPool->GetNextNode(start_from_top, next_node); + int hole_size = next_node->Size; + if (node1 && node1->IsFree()) { + hole_size += node1->Size; + } + if (node2 && node2->IsFree()) { + hole_size += node2->Size; + } + if (hole_size > best_hole_size) { + best_hole_size = hole_size; + movement->Size = next_node->Size; + movement->Address = next_node->Address; + movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); + first = false; + } + } + } + } else if (filler_method == 1) { + bool done = false; + bool found_one = false; + bool found_big_enough = false; + TSMemoryNode *largest_allocated = 0; + int current_best = 0; + int current_best_middle_memory = 0x3E8000; + int best_address = 0; + bool first_pass = true; + TSMemoryNode *top_free_top = 0; - int n = 0; - while (n < NumTrackStreamingSections) { - TrackStreamingSection *section = &pTrackStreamingSections[n]; - int boundary_section_number = GetBoundarySectionNumber(static_cast(section->SectionNumber), bGetPlatformName()); - VisibleSectionBoundary *boundary = TheVisibleSectionManager.FindBoundary(boundary_section_number); + do { + if (first_pass) { + first_pass = false; + top_free_top = pMemoryPool->GetFirstNode(true); + } else { + top_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); + } - section->pBoundary = boundary; - n += 1; - } + if (!top_free_top) { + done = true; + } else { + int top_free_memory = top_free_top->Size; + TSMemoryNode *bottom_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); + if (!bottom_free_top) { + done = true; + } else { + TSMemoryNode *top_allocated = pMemoryPool->GetNextNode(true, top_free_top); + pMemoryPool->GetNextNode(false, bottom_free_top); - EmptyCaffeineLayers(); -} + int middle_allocated_memory = top_allocated->Size; + int total_free_memory = top_free_memory + bottom_free_top->Size; + int size_checking[32]; + int i = 0; + do { + size_checking[i] = 0; + i += 1; + } while (i < 32); -void TrackStreamer::PlotLoadingMarker(StreamingPositionEntry *streaming_position) { - char stack[0x20]; - (void)stack; -} + size_checking[0] = top_allocated->Size; -void TrackStreamer::SwitchZones(short *current_zones) { - StartLoadingTime = GetDebugRealTime(); - CurrentZoneNeedsRefreshing = false; + TSMemoryNode *largest_allocated_here = top_allocated; + TSMemoryNode *cursor = top_allocated; + int found_nodes = 1; + while ((cursor = pMemoryPool->GetNextNode(true, top_allocated)) != bottom_free_top && top_allocated && found_nodes < 32) { + top_allocated = pMemoryPool->GetNextNode(true, top_allocated); + if (top_allocated) { + size_checking[found_nodes] = top_allocated->Size; + middle_allocated_memory += top_allocated->Size; + found_nodes += 1; + if (top_allocated->Size > largest_allocated_here->Size) { + largest_allocated_here = top_allocated; + } + } + } - for (int position_number = 0; position_number < 2; position_number++) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - int zone_number = current_zones[position_number]; - if (position_entry->CurrentZone != zone_number) { - PlotLoadingMarker(position_entry); + int free_gap = total_free_memory - middle_allocated_memory; + if ((!found_big_enough && current_best < free_gap) || + (found_big_enough && total_free_memory + middle_allocated_memory >= total_needing_allocation && + middle_allocated_memory < current_best_middle_memory)) { + std::sort(size_checking, size_checking + found_nodes); + int evaluated_best_address = 0; + bool largest_flag = false; + int nodes_to_move = 0; + int position = 0; - VisibleSectionBoundary *boundary1 = TheVisibleSectionManager.FindBoundary(position_entry->CurrentZone); - VisibleSectionBoundary *boundary2 = TheVisibleSectionManager.FindBoundary(zone_number); - float best_distance = kMaxDistance_TrackStreamer; - if (boundary1 && boundary2) { - for (int n = 0; n < boundary1->GetNumPoints(); n++) { - float distance = boundary2->GetDistanceOutside(boundary1->GetPoint(n), kMaxDistance_TrackStreamer); - best_distance = bMin(best_distance, distance); + TSMemoryNode *evaluated_top_free = pMemoryPool->GetFirstFreeNode(true); + while (found_nodes > nodes_to_move && evaluated_top_free) { + bool skip_flag = false; + int target_index = found_nodes - nodes_to_move - 1; + for (int i = 0; i < found_nodes; i++) { + if (size_checking[target_index] == position) { + skip_flag = true; + } + } + if (evaluated_top_free == top_free_top || evaluated_top_free == bottom_free_top) { + skip_flag = true; + } + if (!skip_flag && evaluated_top_free->Size >= size_checking[target_index]) { + size_checking[target_index] = position; + nodes_to_move += 1; + if (!largest_flag) { + evaluated_best_address = evaluated_top_free->Address; + largest_flag = true; + } + evaluated_top_free = pMemoryPool->GetNextFreeNode(true, 0); + } + evaluated_top_free = pMemoryPool->GetNextFreeNode(true, evaluated_top_free); + position += 1; + } + + if (nodes_to_move >= found_nodes) { + current_best = free_gap; + best_address = evaluated_best_address; + found_one = true; + largest_allocated = largest_allocated_here; + if (total_free_memory + middle_allocated_memory >= total_needing_allocation) { + found_big_enough = true; + current_best_middle_memory = middle_allocated_memory; + } + } + } + } } - } + } while (!done); - if (kSwitchZoneFarLoadThreshold_TrackStreamer < best_distance) { - CurrentZoneFarLoad = true; + if (found_one && largest_allocated && FindSectionByAddress(largest_allocated->Address)) { + movement->Size = largest_allocated->Size; + movement->Address = largest_allocated->Address; + movement->NewAddress = best_address; } + } - position_entry->CurrentZone = zone_number; - position_entry->BeginLoadingPosition = position_entry->Position; - position_entry->BeginLoadingTime = GetDebugRealTime(); - position_entry->NumSectionsToLoad = 0; - position_entry->NumSectionsLoaded = 0; - position_entry->AmountToLoad = 0; - position_entry->AmountLoaded = 0; + if (movement->Address == 0) { + failed = true; + break; } - if (position_number == 0) { - GetScenerySectionName(CurrentZoneName, zone_number); - } else if (zone_number > 0) { - bSPrintf(CurrentZoneName, "%s - %s", CurrentZoneName, GetScenerySectionName(zone_number)); + num_movements += 1; + movement->Checksum = pMemoryPool->GetPoolChecksum(); + pMemoryPool->Free(reinterpret_cast(movement->Address)); + pMemoryPool->Malloc(movement->Size, "HoleMovement", false, false, movement->NewAddress); + amount_moved += movement->Size; + if (max_amount_to_move < amount_moved) { + failed = true; + break; } } - int num_sections_unactivated = 0; - DetermineStreamingSections(); - PostLoadFixupDisabled = true; - for (int n = 0; n < NumTrackStreamingSections; n++) { - TrackStreamingSection *section = &pTrackStreamingSections[n]; - if (section->Status == TrackStreamingSection::ACTIVATED && !section->CurrentlyVisible) { - if (!IsTextureSection(section->SectionNumber) && !IsLibrarySection(section->SectionNumber)) { - UnactivateSection(section); - num_sections_unactivated += 1; - } + for (int n = num_movements - 1; n >= 0; n--) { + HoleMovement *movement = &hole_movements[n]; + pMemoryPool->Free(reinterpret_cast(movement->NewAddress)); + TrackStreamingSection *section = FindSectionByAddress(movement->Address); + char *debug_name; + if (section) { + debug_name = section->SectionName; + } else { + debug_name = "UndoHoleMovement"; } + pMemoryPool->Malloc(movement->Size, debug_name, false, false, movement->Address); } - PostLoadFixupDisabled = false; - if (num_sections_unactivated > 0) { - PostLoadFixup(); - SkipNextHandleLoad = true; + pMemoryPool->EnableTracing(true); + if (pamount_moved) { + *pamount_moved = amount_moved; } - - FreeSectionMemory(); - SetLoadingPhase(ALLOCATING_TEXTURE_SECTIONS); - NumJettisonedSections = 0; - CurrentZoneOutOfMemory = false; - CurrentZoneAllocatedButIncomplete = false; - MemorySafetyMargin = 0; - AmountJettisoned = 0; - bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); - AssignLoadingPriority(); - CalculateLoadingBacklog(); + if (failed) { + return -1; + } + return num_movements; } -int TrackStreamer::Loader(bChunk *chunk) { - unsigned int chunk_id = chunk->GetID(); - if (chunk_id == 0x34110) { - pTrackStreamingSections = reinterpret_cast(chunk->GetData()); - NumTrackStreamingSections = chunk->Size / sizeof(TrackStreamingSection); - for (int i = 0; i < NumTrackStreamingSections; i++) { - TrackStreamingSection *section = &pTrackStreamingSections[i]; - bEndianSwap16(§ion->SectionNumber); - bEndianSwap32(§ion->FileType); - bEndianSwap32(§ion->Status); - bEndianSwap32(§ion->FileOffset); - bEndianSwap32(§ion->Size); - bEndianSwap32(§ion->CompressedSize); - bEndianSwap32(§ion->PermSize); - bEndianSwap32(§ion->SectionPriority); - bPlatEndianSwap(§ion->Centre); - bEndianSwap32(§ion->Radius); - bEndianSwap32(§ion->Checksum); - } - - for (int i = 0; i < NumHibernatingSections; i++) { - TrackStreamingSection *src = &HibernatingSections[i]; - TrackStreamingSection *section = FindSection(src->SectionNumber); - bMemCpy(section, src, sizeof(TrackStreamingSection)); - NumSectionsLoaded += 1; - ActivateSection(section); - int current_streaming_section = NumCurrentStreamingSections; - CurrentStreamingSections[current_streaming_section] = section; - NumCurrentStreamingSections = current_streaming_section + 1; - } +int TrackStreamer::DoHoleFilling(int largest_free) { + ProfileNode profile_node("TODO", 0); + const char *fragmented_user_allocation; + HoleMovement hole_movement_table[128]; - NumHibernatingSections = 0; - return 1; - } else if (chunk_id == 0x34113) { - pDiscBundleSections = reinterpret_cast(chunk->GetData()); - pLastDiscBundleSection = reinterpret_cast(reinterpret_cast(pDiscBundleSections) + chunk->Size); - for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; - disc_bundle = reinterpret_cast(reinterpret_cast(disc_bundle) + - (disc_bundle->NumMembers * sizeof(DiscBundleSectionMember) + 0x14))) { - bEndianSwap32(&disc_bundle->FileOffset); - bEndianSwap32(&disc_bundle->FileSize); - for (int i = 0; i < disc_bundle->NumMembers; i++) { - DiscBundleSectionMember *member = &disc_bundle->Members[i]; - bEndianSwap16(&member->SectionNumber); - bEndianSwap16(&member->FileOffset); - member->pSection = FindSection(member->SectionNumber); - } - } - return 1; - } else if (chunk_id == 0x34111) { - pInfo = reinterpret_cast(chunk->GetData()); - for (int i = 0; i < 2; i++) { - bEndianSwap32(i + pInfo->FileSize); - } - return 1; - } else if (chunk_id == 0x34112) { - pBarriers = reinterpret_cast(chunk->GetData()); - NumBarriers = chunk->Size / sizeof(TrackStreamingBarrier); - for (int i = 0; i < NumBarriers; i++) { - TrackStreamingBarrier *barrier = &pBarriers[i]; - bPlatEndianSwap(&barrier->Points[0]); - bPlatEndianSwap(&barrier->Points[1]); - } - return 1; - } else { + CountUserAllocations(&fragmented_user_allocation); + if (fragmented_user_allocation) { + pMemoryPool->DebugPrint(); return 0; } -} - -int TrackStreamer::Unloader(bChunk *chunk) { - unsigned int chunk_id = chunk->GetID(); - if (chunk_id == 0x34110) { - UnloadEverything(); - pTrackStreamingSections = 0; - NumTrackStreamingSections = 0; - return 1; - } - if (chunk_id == 0x34113) { - pDiscBundleSections = 0; - pLastDiscBundleSection = 0; - return 1; + int best_method = -1; + int forced_hole_filler_method = ForceHoleFillerMethod; + if (forced_hole_filler_method >= 0) { + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, forced_hole_filler_method, largest_free, 0, 0x7FFFFFFF); + if (num_hole_movements > 0) { + best_method = forced_hole_filler_method; + } + } else { + int best_amount_moved = 0x7FFFFFFF; + for (int filler_method = 1; filler_method < 6; filler_method++) { + int amount_moved; + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, filler_method, largest_free, &amount_moved, best_amount_moved); + if (num_hole_movements > 0 && amount_moved < best_amount_moved) { + best_method = filler_method; + best_amount_moved = amount_moved; + } + } } - if (chunk_id == 0x34111) { - pInfo = 0; - return 1; + if (best_method < 0) { + return 0; } - if (chunk_id == 0x34112) { - pBarriers = 0; - NumBarriers = 0; - return 1; + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, best_method, largest_free, 0, 0x7FFFFFFF); + for (int n = 0; n < num_hole_movements; n++) { + ProfileNode profile_node("TODO", 0); + HoleMovement *movement = &hole_movement_table[n]; + TrackStreamingSection *section = FindSectionByAddress(movement->Address); + if (LastWaitUntilRenderingDoneFrameCount != eGetFrameCounter()) { + int start_ticks = bGetTicker(); + DisableWaitForFrameBufferSwap(); + eWaitUntilRenderingDone(); + LastWaitUntilRenderingDoneFrameCount = eGetFrameCounter(); + EnableWaitForFrameBufferSwap(); + float time = bGetTickerDifference(start_ticks); + (void)time; + } + + int start_ticks = bGetTicker(); + void *new_memory = reinterpret_cast(movement->NewAddress); + pMemoryPool->Free(reinterpret_cast(movement->Address)); + pMemoryPool->Malloc(movement->Size, section->SectionName, false, false, movement->NewAddress); + if (section->Status == TrackStreamingSection::ACTIVATED) { + eAllowDuplicateSolids(true); + SetDuplicateTextureWarning(false); + MoveChunks(reinterpret_cast(new_memory), reinterpret_cast(section->pMemory), section->LoadedSize, + section->SectionName); +#ifdef EA_PLATFORM_GAMECUBE + DCStoreRangeNoSync(new_memory, section->LoadedSize); +#endif + eAllowDuplicateSolids(false); + SetDuplicateTextureWarning(true); + } else { + eWaitUntilRenderingDone(); + bOverlappedMemCpy(new_memory, section->pMemory, section->LoadedSize); + } + section->pMemory = new_memory; + float move_time = bGetTickerDifference(start_ticks); + (void)move_time; + NumSectionsMoved += 1; +#ifdef EA_PLATFORM_GAMECUBE + PPCSync(); +#endif } - return 0; + return 1; } -void TrackStreamer::HibernateStreamingSections() { - int sections_to_hibernate[5]; - int n; - int section_number; - TrackStreamingSection *section; - TrackStreamingSection *hibernating_section; - - (void)sections_to_hibernate; - (void)n; - (void)section_number; - (void)section; - (void)hibernating_section; - return; +void TrackStreamer::SetStreamingPosition(int position_number, const bVector3 *position) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->Position.x = position->x; + position_entry->Position.y = position->y; + position_entry->PredictedZone = 0; + position_entry->Elevation = position->z; + position_entry->Direction.y = 0.0f; + position_entry->PredictedZoneValidTime = -1; + position_entry->Velocity.x = 0.0f; + position_entry->Velocity.y = 0.0f; + position_entry->Direction.x = 0.0f; + position_entry->PositionSet = true; + position_entry->FollowingCar = false; + CurrentZoneNeedsRefreshing = true; } -void TrackStreamer::FlushHibernatingSections() { - for (int n = 0; n < NumHibernatingSections; n++) { - TrackStreamingSection *section = &HibernatingSections[n]; - bFree(section->pMemory); - } - NumHibernatingSections = 0; +void TrackStreamer::PredictStreamingPosition(int position_number, const bVector3 *position, const bVector3 *velocity, const bVector3 *direction, + bool following_car) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->Position.x = position->x; + position_entry->Position.y = position->y; + position_entry->Elevation = position->z; + position_entry->Velocity.x = velocity->x; + position_entry->Velocity.y = velocity->y; + position_entry->Direction.x = direction->x; + float direction_y = direction->y; + position_entry->FollowingCar = following_car; + position_entry->Direction.y = direction_y; + position_entry->PositionSet = true; } -bool TrackStreamer::NeedsGameStateActivation(TrackStreamingSection *section) { - return false; - - if (IsRegularScenerySection(section->SectionNumber) && IsLODScenerySectionNumber(section->SectionNumber)) { - return true; - } +void TrackStreamer::ReadyToMakeSpaceInPoolBridge(int param) { + reinterpret_cast(param)->ReadyToMakeSpaceInPool(); } -void TrackStreamer::FreeSectionMemory() { - NumSectionsOutOfMemory = 0; - for (int n = 0; n < NumTrackStreamingSections; n++) { - TrackStreamingSection *section = &pTrackStreamingSections[n]; - if (section->Status == TrackStreamingSection::ALLOCATED) { - bFree(section->pMemory); - section->pDiscBundle = 0; - section->pMemory = 0; - section->Status = TrackStreamingSection::UNLOADED; - } - } -} +short TrackStreamer::GetPredictedZone(StreamingPositionEntry *position_entry) { + float speed = bLength(&position_entry->Velocity); + int predicted_zone = 0; + bool found_predicted_zone = false; + TrackPathZone *zone = 0; + bVector2 predict_position; -int TrackStreamer::GetSectionToActivate(int activation_delay) { - if (NumSectionsActivated < NumCurrentStreamingSections) { - for (int n = 0; n < NumCurrentStreamingSections; n++) { - TrackStreamingSection *section = CurrentStreamingSections[n]; - if (section->Status == TrackStreamingSection::LOADED && TheTrackStreamer.NeedsGameStateActivation(section) && - RealTimeFrames - section->LoadedTime >= activation_delay) { - return section->SectionNumber; - } + while ((zone = TheTrackPathManager.FindZone(&position_entry->Position, TRACK_PATH_ZONE_STREAMER_PREDICTION, zone))) { + float elevation = zone->GetElevation(); + if ((0.0f < elevation && position_entry->Elevation < elevation) || (elevation < 0.0f && -elevation < position_entry->Elevation)) { + continue; } - } - - return 0; -} -int TrackStreamer::GetCombinedSectionNumber(int section_number) { - bool use_combined_section = false; - if ((static_cast(section_number / 100 - 1) & 0xFF) < 0x14) { - int subsection_number = section_number % 100; - use_combined_section = subsection_number > 0 && subsection_number < ScenerySectionLODOffset; - } + float max_speed = kPredictedZoneStopProjectSpeed_TrackStreamer * kPredictedZoneScale_TrackStreamer; + float distance = speed * kPredictedZoneScale_TrackStreamer; + DrivableScenerySection *scenery_section; + if (max_speed < speed) { + predict_position = position_entry->Position; + } else if (kPredictedZoneMaxDistance_TrackStreamer < distance) { + bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneMaxDistance_TrackStreamer / speed); + } else { + bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneScale_TrackStreamer); + } - if (use_combined_section) { - int combined_section_number = section_number + ScenerySectionLODOffset; - TrackStreamingSection *section = FindSection(section_number); - if (!section) { - section = FindSection(combined_section_number); - if (section) { - return combined_section_number; + scenery_section = TheVisibleSectionManager.FindDrivableSection(&predict_position); + if (scenery_section && zone->Data[0] != 0) { + short section_number = scenery_section->SectionNumber; + for (int i = 0; i <= 3; i++) { + if (zone->Data[i] == 0) { + break; + } + if (zone->Data[i] == section_number) { + found_predicted_zone = true; + predicted_zone = section_number; + break; + } } } } - return section_number; -} - -void TrackStreamer::HandleSectionActivation() { - ProfileNode profile_node("TODO", 0); - int activation_delay; - short section_to_activate = static_cast(GetSectionToActivate(0)); - (void)activation_delay; - if (section_to_activate != 0) { - TrackStreamingSection *section = FindSection(section_to_activate); - if (section->Status != TrackStreamingSection::ACTIVATED) { - if (section->Status != TrackStreamingSection::LOADED) { - if (!section->CurrentlyVisible) { - return; + if (found_predicted_zone) { + if (!bEqual(&predict_position, &position_entry->Position, kPredictedZoneEqualEpsilon_TrackStreamer)) { + for (int barrier_num = 0; barrier_num < NumBarriers; barrier_num++) { + TrackStreamingBarrier *barrier = &pBarriers[barrier_num]; + if (barrier->Intersects(&position_entry->Position, &predict_position)) { + found_predicted_zone = false; + predicted_zone = 0; } - - do { - HandleLoading(); - ServiceResourceLoading(); - } while (section->Status != TrackStreamingSection::LOADED); } - ActivateSection(section); + } + + if (found_predicted_zone) { + return predicted_zone; } } + + DrivableScenerySection *scenery_section = TheVisibleSectionManager.FindDrivableSection(&position_entry->Position); + if (scenery_section) { + predicted_zone = scenery_section->SectionNumber; + } + return predicted_zone; } -void TrackStreamer::UnloadEverything() { - while (NumSectionsLoading != 0) { - ServiceResourceLoading(); +void TrackStreamer::ClearStreamingPositions() { + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->PositionSet = false; + position_entry->FollowingCar = false; } +} - for (int n = 0; n < NumTrackStreamingSections; n++) { - TrackStreamingSection *section = &pTrackStreamingSections[n]; - if (static_cast(section->Status - TrackStreamingSection::LOADED) < 2U) { - UnloadSection(section); - } +void TrackStreamer::RemoveCurrentStreamingSections() { + for (int i = 0; i < NumCurrentStreamingSections; i++) { + CurrentStreamingSections[i]->CurrentlyVisible = 0; } - FreeSectionMemory(); - ClearCurrentZones(); + NumCurrentStreamingSections = 0; + bBitTableLayout_TrackStreamer *layout = reinterpret_cast(&CurrentVisibleSectionTable); + bMemSet(layout->Bits, 0, layout->NumBits >> 3); } -void TrackStreamer::ActivateSection(TrackStreamingSection *section) { - ProfileNode profile_node(section->SectionName, 0); - int allocation_params = 0x2087; - NumSectionsActivated += 1; - eAllowDuplicateSolids(true); - SetDuplicateTextureWarning(false); +void TrackStreamer::AddCurrentStreamingSections(short *section_numbers, int num_sections, int position_number) { + int i = 0; + if (i < num_sections) { + StreamingPositionEntry *streaming_position = &StreamingPositionEntries[position_number]; + unsigned int position_bit = 1 << position_number; + do { + short §ion_number = section_numbers[i]; + CurrentVisibleSectionTable.Set(section_number); + if (SplitScreen) { + section_number = static_cast(Get2PlayerSectionNumber(section_number)); + } - bChunk *chunks = reinterpret_cast(section->pMemory); - int sizeof_chunks = section->LoadedSize; - LoadTempPermChunks(&chunks, &sizeof_chunks, allocation_params, section->SectionName); + TrackStreamingSection *section = FindSection(section_number); + if (!section) { + continue; + } + + section->LastNeededTimestamp = RealTimeFrames; + if (!section->CurrentlyVisible) { + CurrentStreamingSections[NumCurrentStreamingSections++] = section; + } - section->pMemory = chunks; - section->LoadedSize = sizeof_chunks; - section->Status = TrackStreamingSection::ACTIVATED; - section->LoadedTime = 0; - eAllowDuplicateSolids(false); - SetDuplicateTextureWarning(true); + if ((((static_cast(section->CurrentlyVisible) >> position_number) ^ 1U) & 1) != 0) { + section->CurrentlyVisible |= static_cast(position_bit); + if (section->Status < TrackStreamingSection::LOADED) { + streaming_position->NumSectionsToLoad += 1; + streaming_position->AmountToLoad += section->Size; + } + } + i += 1; + } while (i < num_sections); + } } -void TrackStreamer::UnactivateSection(TrackStreamingSection *section) { - ProfileNode profile_node(section->SectionName, 0); - section->UnactivatedFrameCount = 0; - DisableWaitUntilRenderingDone(); - section->UnactivatedFrameCount = eGetFrameCounter(); - UnloadChunks(reinterpret_cast(section->pMemory), section->LoadedSize, section->SectionName); - EnableWaitUntilRenderingDone(); - NumSectionsActivated -= 1; - section->Status = TrackStreamingSection::LOADED; -} +void TrackStreamer::DetermineStreamingSections() { + const int max_sections_to_load = 0x180; + short sections_to_load[384]; + int num_sections_to_load = 3; + unsigned short section_number; -void TrackStreamer::LoadDiscBundle(DiscBundleSection *disc_bundle) { - void *memory = 0; - for (int i = 0; i < disc_bundle->NumMembers; i++) { - DiscBundleSectionMember *member = &disc_bundle->Members[i]; - TrackStreamingSection *section = member->pSection; - if (i == 0) { - memory = section->pMemory; + RemoveCurrentStreamingSections(); + sections_to_load[0] = GetScenerySectionNumber_TrackStreamer('Y', 0); + sections_to_load[1] = GetScenerySectionNumber_TrackStreamer('X', 0); + sections_to_load[2] = GetScenerySectionNumber_TrackStreamer('Z', 0); + + { + short *sections_to_load_ptr = sections_to_load; + if (SeeulatorToolActive && ScenerySectionToBlink != 0) { + num_sections_to_load = 4; + sections_to_load_ptr[3] = static_cast(ScenerySectionToBlink); } - section->Status = TrackStreamingSection::LOADING; - } - NumSectionsLoading += 1; - AddQueuedFile(memory, StreamFilenames[1], disc_bundle->FileOffset, disc_bundle->FileSize, DiscBundleLoadedCallback, - reinterpret_cast(disc_bundle), 0); -} + for (int n = 0; n < 4; n++) { + section_number = KeepSectionTable[n]; + if (section_number != 0) { + sections_to_load_ptr[num_sections_to_load] = section_number; + num_sections_to_load += 1; + } + } -void TrackStreamer::DiscBundleLoadedCallback(int param, int error_status) { - (void)error_status; - TheTrackStreamer.DiscBundleLoadedCallback(reinterpret_cast(param)); -} + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 0); + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 1); + int position_number = 0; + do { + { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (position_entry->CurrentZone > 0) { + { + LoadingSection *loading_section = TheVisibleSectionManager.FindLoadingSection(position_entry->CurrentZone); + if (!loading_section) { + { + DrivableScenerySection *drivable_section = TheVisibleSectionManager.FindDrivableSection(position_entry->CurrentZone); + num_sections_to_load = 0; + for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { + { + int section_number = drivable_section->GetVisibleSection(i); + sections_to_load_ptr[num_sections_to_load] = section_number; + num_sections_to_load += 1; + } + } + } + } else { + num_sections_to_load = + TheVisibleSectionManager.GetSectionsToLoad(loading_section, sections_to_load_ptr, max_sections_to_load); + } + } -void TrackStreamer::DiscBundleLoadedCallback(DiscBundleSection *disc_bundle) { - NumSectionsLoading += -1 + disc_bundle->NumMembers; - for (int i = 0; i < disc_bundle->NumMembers; i++) { - DiscBundleSectionMember *member = &disc_bundle->Members[i]; - TrackStreamingSection *section = member->pSection; - section->pDiscBundle = 0; - SectionLoadedCallback(section); + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, position_number); + } + } + position_number += 1; + } while (position_number < 2); } } -void TrackStreamer::LoadSection(TrackStreamingSection *section) { - NumSectionsLoading += 1; - section->Status = TrackStreamingSection::LOADING; +int TrackStreamer::AllocateSectionMemory(int *ptotal_needing_allocation) { + ProfileNode profile_node("TODO", 0); + int out_of_memory_size = 0; + int total_needing_allocation = 0; + int num_sections_allocated = 0; - if (section->CompressedSize == section->Size) { - AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, - reinterpret_cast(section), 0); - } else { - QueuedFileParams params; - params.BlockSize = 0x7ffffff; - params.Priority = QueuedFileDefaultPriority; - params.Compressed = false; - params.Compressed = true; - params.UncompressedSize = section->Size; - AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, - reinterpret_cast(section), ¶ms); - } -} + if (LoadingPhase == ALLOCATING_REGULAR_SECTIONS && pDiscBundleSections < pLastDiscBundleSection) { + for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; + disc_bundle = disc_bundle->GetMemoryImageNext()) { + int i = 0; + if (disc_bundle->NumMembers > 0) { + do { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + if (!section->CurrentlyVisible || section->Status != TrackStreamingSection::UNLOADED) { + break; + } + i += 1; + } while (i < disc_bundle->NumMembers); + } -void TrackStreamer::SectionLoadedCallback(int param, int error_status) { - (void)error_status; - TheTrackStreamer.SectionLoadedCallback(reinterpret_cast(param)); -} + if (i == disc_bundle->NumMembers) { + if (disc_bundle->FileSize <= pMemoryPool->GetLargestFreeBlock()) { + unsigned char *pmemory = + static_cast(pMemoryPool->Malloc(disc_bundle->FileSize, disc_bundle->SectionName, true, false, 0)); + pMemoryPool->Free(pmemory); -void TrackStreamer::SectionLoadedCallback(TrackStreamingSection *section) { - section->Status = TrackStreamingSection::LOADED; - section->LoadedSize = section->Size; - EndianSwapChunkHeadersRecursive(reinterpret_cast(section->pMemory), section->Size); - NumSectionsLoading -= 1; - NumSectionsLoaded += 1; - section->LoadedTime = RealTimeFrames; + for (i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + void *realloc_mem = pmemory + member->FileOffset * 0x80; - if (section->CurrentlyVisible && !NeedsGameStateActivation(section)) { - ActivateSection(section); - } + num_sections_allocated += 1; + section->pDiscBundle = disc_bundle; + section->Status = TrackStreamingSection::ALLOCATED; + section->pMemory = realloc_mem; + pMemoryPool->Malloc(section->Size, disc_bundle->SectionName, false, false, reinterpret_cast(realloc_mem)); + } - for (int position_number = 0; position_number < 2; position_number++) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - if (((section->CurrentlyVisible >> position_number) & 1) != 0) { - position_entry->NumSectionsLoaded += 1; - position_entry->AmountLoaded += section->Size; + total_needing_allocation += disc_bundle->FileSize; + } + } } } - CalculateLoadingBacklog(); -#ifdef EA_PLATFORM_GAMECUBE - DCStoreRange(section->pMemory, section->LoadedSize); -#endif -} - -TrackStreamingSection *TrackStreamer::ChooseSectionToJettison() { - TrackStreamingSection *best_section = 0; - int best_discard_priority = 0; - static int last_jettison_print; - bool print_jettison_this_frame = false; - - last_jettison_print = RealLoopCounter; for (int i = 0; i < NumCurrentStreamingSections; i++) { - int discard_priority = 0; TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->Status != TrackStreamingSection::UNLOADED) { + continue; + } - if (IsTextureSection(section->SectionNumber) || IsLibrarySection(section->SectionNumber)) { - discard_priority = 2; - if (section->SectionNumber == GetScenerySectionNumber('Y', 0) || section->SectionNumber == GetScenerySectionNumber('W', 0) || - section->SectionNumber == GetScenerySectionNumber('X', 0)) { - discard_priority = 1; - } else if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS && IsTextureSection(section->SectionNumber) && - section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { - discard_priority = 10000; - } else if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS && IsLibrarySection(section->SectionNumber) && - section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { - discard_priority = 10000; + if (section->SectionNumber != GetScenerySectionNumber('Y', 0) && section->SectionNumber != GetScenerySectionNumber('X', 0) && + section->SectionNumber != GetScenerySectionNumber('W', 0) && section->SectionNumber != GetScenerySectionNumber('U', 0) && + section->SectionNumber != GetScenerySectionNumber('Z', 0)) { + if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS) { + if (!IsTextureSection(section->SectionNumber)) { + continue; + } } - } else if (IsRegularScenerySection(section->SectionNumber)) { - int loading_priority = GetLoadingPriority(section, &StreamingPositionEntries[0], true); - if (SplitScreen) { - int loading_priority2 = GetLoadingPriority(section, &StreamingPositionEntries[1], true); - if (loading_priority2 < loading_priority) { - loading_priority = loading_priority2; + + if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS) { + if (!IsLibrarySection(section->SectionNumber)) { + continue; } } - discard_priority = loading_priority * 10 + 100; } - if (discard_priority != 0) { - if (static_cast(section->Status - TrackStreamingSection::LOADED) > 1) { - discard_priority += 1; + total_needing_allocation += section->Size; + if (bLargestMalloc(7) < section->Size) { + out_of_memory_size += section->Size; + NumSectionsOutOfMemory += 1; + } else { + num_sections_allocated += 1; + section->pMemory = AllocateMemory(section, 0x80); + section->Status = TrackStreamingSection::ALLOCATED; + if (num_sections_allocated > 99999) { + CurrentZoneAllocatedButIncomplete = true; + return out_of_memory_size; } } - if (discard_priority > best_discard_priority) { - best_section = section; - best_discard_priority = discard_priority; - } } - return best_section; + CurrentZoneAllocatedButIncomplete = false; + *ptotal_needing_allocation = total_needing_allocation; + return out_of_memory_size; } -void TrackStreamer::UnJettisonSections() { - for (int n = 0; n < NumJettisonedSections; n++) { - TrackStreamingSection *section = JettisonedSections[n]; - CurrentStreamingSections[NumCurrentStreamingSections++] = section; - section->CurrentlyVisible = true; +void TrackStreamer::FreeSectionMemory() { + NumSectionsOutOfMemory = 0; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ALLOCATED) { + bFree(section->pMemory); + section->pDiscBundle = 0; + section->pMemory = 0; + section->Status = TrackStreamingSection::UNLOADED; + } } - NumJettisonedSections = 0; - AmountJettisoned = 0; } bool TrackStreamer::HandleMemoryAllocation() { @@ -1895,72 +1863,269 @@ bool TrackStreamer::HandleMemoryAllocation() { return true; } -void TrackStreamer::StartLoadingSections() { - bool something_to_load = true; - while (NumSectionsLoading < 2 && something_to_load) { - int best_priority = 0x7FFFFFFF; - TrackStreamingSection *best_section = 0; - for (int i = 0; i < NumCurrentStreamingSections; i++) { - TrackStreamingSection *section = CurrentStreamingSections[i]; - if (section->Status == TrackStreamingSection::ALLOCATED) { - int priority = section->LoadingPriority; - if (section->pDiscBundle) { - priority = -1; - } - if (priority < best_priority) { - best_priority = priority; - best_section = section; - } - } else if (section->Status == TrackStreamingSection::LOADED && !NeedsGameStateActivation(section)) { - TheTrackStreamer.ActivateSection(section); - } +void *TrackStreamer::AllocateUserMemory(int size, const char *debug_name, int offset) { +#ifndef MILESTONE_OPT + (void)debug_name; +#endif + + int allocation_params; + if (size > bLargestMalloc(7)) { + allocation_params = (offset & 0x1FFC) << 17 | 0x2000; + } else { + allocation_params = (offset & 0x1FFC) << 17 | 0x2047; + } +#ifdef MILESTONE_OPT + return bMalloc(size, debug_name, 0, allocation_params); +#else + return bMalloc(size, allocation_params); +#endif +} + +void TrackStreamer::FreeUserMemory(void *mem) { + int free_before = pMemoryPool->GetAmountFree(); + bFree(mem); + int size = pMemoryPool->GetAmountFree(); + (void)free_before; + (void)size; +} + +bool TrackStreamer::IsUserMemory(void *mem) { + int pos = static_cast(mem) - static_cast(pMemoryPoolMem); + return pMemoryPoolMem && pos >= 0 && pos < MemoryPoolSize; +} + +bool TrackStreamer::MakeSpaceInPool(int size, bool force_unloading) { + WaitForCurrentLoadingToComplete(); + while (bCountFreeMemory(7) < size) { + int amount_unloaded = UnloadLeastRecentlyUsedSection(); + if (amount_unloaded == 0 && (!force_unloading || !JettisonLeastImportantSection())) { + break; } + } - if (!best_section) { - something_to_load = false; + ForceHoleFillerMethod = 0; + DoHoleFilling(0x7FFFFFFF); + ForceHoleFillerMethod = -1; + return size <= bLargestMalloc(7); +} + +void TrackStreamer::MakeSpaceInPool(int size, void (*callback)(int), int param) { + if (LoadingPhase == LOADING_IDLE) { + IsLoadingInProgress(); + } + + if (!IsLoadingInProgress()) { + MakeSpaceInPool(size, true); + callback(param); + } else { + MakeSpaceInPoolSize = size; + MakeSpaceInPoolCallback = callback; + MakeSpaceInPoolCallbackParam = param; + pCallback = ReadyToMakeSpaceInPoolBridge; + CallbackParam = reinterpret_cast(this); + } +} + +void TrackStreamer::ReadyToMakeSpaceInPool() { + MakeSpaceInPool(MakeSpaceInPoolSize, true); + + void (*callback)(int) = MakeSpaceInPoolCallback; + int param = MakeSpaceInPoolCallbackParam; + MakeSpaceInPoolCallback = 0; + MakeSpaceInPoolCallbackParam = 0; + MakeSpaceInPoolSize = 0; + callback(param); +} + +bool TrackStreamer::DetermineCurrentZones(short *current_zones) { + bool changed = false; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + short current_zone = -1; + if (position_entry->PositionSet) { + current_zone = GetPredictedZone(position_entry); + } + + if (current_zone == position_entry->PredictedZone) { + position_entry->PredictedZoneValidTime += 1; + } else if (position_entry->PredictedZoneValidTime == -1) { + position_entry->PredictedZone = current_zone; + position_entry->PredictedZoneValidTime = 1000; } else { - if (best_section->pDiscBundle) { - DiscBundleSection *disc_bundle = best_section->pDiscBundle; - LoadDiscBundle(disc_bundle); - } else { - LoadSection(best_section); + position_entry->PredictedZone = current_zone; + position_entry->PredictedZoneValidTime = 1; + } + + short section_number = position_entry->CurrentZone; + if (current_zone != section_number) { + if (position_entry->PredictedZoneValidTime < 0) { + current_zone = section_number; + } + if (current_zone != section_number) { + changed = true; } } + + current_zones[position_number] = current_zone; } + + return changed || CurrentZoneNeedsRefreshing; } -void TrackStreamer::FinishedLoading() { - { - float load_time; - int position_number; - StreamingPositionEntry *position_entry; - (void)load_time; - (void)position_number; - (void)position_entry; +void TrackStreamer::ServiceGameState() { + float start_time = GetDebugRealTime(); + HandleZoneSwitching(); + HandleSectionActivation(); + float time = GetDebugRealTime(); + (void)start_time; + (void)time; + + AmountNotRendered = 0; + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + if (!section->WasRendered && IsRegularScenerySection(section->SectionNumber)) { + AmountNotRendered += section->Size; + } + section->WasRendered = 0; + } +} + +void TrackStreamer::ServiceNonGameState() { + ProfileNode profile_node("TODO", 0); + float start_time = GetDebugRealTime(); + HandleLoading(); + float time = GetDebugRealTime(); + (void)start_time; + (void)time; +} + +void TrackStreamer::BlockUntilLoadingComplete() { + RefreshLoading(); + WaitForCurrentLoadingToComplete(); +} + +void TrackStreamer::WaitForCurrentLoadingToComplete() { + while (!AreAllSectionsActivated()) { + HandleLoading(); + short section_to_activate = static_cast(GetSectionToActivate(0)); + if (section_to_activate != 0) { + ActivateSection(FindSection(section_to_activate)); + } + ServiceResourceLoading(); + bThreadYield(8); + } +} + +bool TrackStreamer::IsLoadingInProgress() { + bool loading_in_progress = !AreAllSectionsActivated(); + + if (!loading_in_progress && !AreAllSectionsActivated()) { + while (!AreAllSectionsActivated()) { + ServiceResourceLoading(); + ServiceNonGameState(); + } + } + + return loading_in_progress; +} + +bool TrackStreamer::AreAllSectionsActivated() { + bool all_sections_activated = false; + if (LoadingPhase == LOADING_IDLE) { + all_sections_activated = NumCurrentStreamingSections <= NumSectionsActivated + NumSectionsOutOfMemory; + } + return all_sections_activated; +} + +void TrackStreamer::RefreshLoading() { + CurrentZoneNeedsRefreshing = true; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->PredictedZoneValidTime = -1; + } + HandleZoneSwitching(); +} + +void TrackStreamer::HandleZoneSwitching() { + ProfileNode profile_node("TODO", 0); + short current_zones[2]; + bool current_zones_different; + if (!ZoneSwitchingDisabled && pMemoryPoolMem) { + current_zones_different = DetermineCurrentZones(current_zones); + if (current_zones_different) { + SwitchZones(current_zones); + } } +} +void TrackStreamer::SwitchZones(short *current_zones) { + StartLoadingTime = GetDebugRealTime(); + CurrentZoneNeedsRefreshing = false; + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + int zone_number = current_zones[position_number]; + if (position_entry->CurrentZone != zone_number) { + PlotLoadingMarker(position_entry); + + VisibleSectionBoundary *boundary1 = TheVisibleSectionManager.FindBoundary(position_entry->CurrentZone); + VisibleSectionBoundary *boundary2 = TheVisibleSectionManager.FindBoundary(zone_number); + float best_distance = kMaxDistance_TrackStreamer; + if (boundary1 && boundary2) { + for (int n = 0; n < boundary1->GetNumPoints(); n++) { + float distance = boundary2->GetDistanceOutside(boundary1->GetPoint(n), kMaxDistance_TrackStreamer); + best_distance = bMin(best_distance, distance); + } + } + + if (kSwitchZoneFarLoadThreshold_TrackStreamer < best_distance) { + CurrentZoneFarLoad = true; + } + + position_entry->CurrentZone = zone_number; + position_entry->BeginLoadingPosition = position_entry->Position; + position_entry->BeginLoadingTime = GetDebugRealTime(); + position_entry->NumSectionsToLoad = 0; + position_entry->NumSectionsLoaded = 0; + position_entry->AmountToLoad = 0; + position_entry->AmountLoaded = 0; + } - LoadingPhase = LOADING_IDLE; - CurrentZoneNonReplayLoad = false; - CurrentZoneFarLoad = false; - NotifySkyLoader(); + if (position_number == 0) { + GetScenerySectionName(CurrentZoneName, zone_number); + } else if (zone_number > 0) { + bSPrintf(CurrentZoneName, "%s - %s", CurrentZoneName, GetScenerySectionName(zone_number)); + } + } - for (int position_number = 0; position_number < 2; position_number++) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - if (position_entry->BeginLoadingTime != 0.0f) { - PlotLoadingMarker(position_entry); + int num_sections_unactivated = 0; + DetermineStreamingSections(); + PostLoadFixupDisabled = true; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ACTIVATED && !section->CurrentlyVisible) { + if (!IsTextureSection(section->SectionNumber) && !IsLibrarySection(section->SectionNumber)) { + UnactivateSection(section); + num_sections_unactivated += 1; + } } - position_entry->BeginLoadingTime = 0.0f; } + PostLoadFixupDisabled = false; - if (pCallback) { - SetDelayedResourceCallback(pCallback, CallbackParam); - pCallback = 0; - CallbackParam = 0; + if (num_sections_unactivated > 0) { + PostLoadFixup(); + SkipNextHandleLoad = true; } -} -void TrackStreamer::EmptyCaffeineLayers() { - TrackStreamerRemoteCaffeinating = 0; + FreeSectionMemory(); + SetLoadingPhase(ALLOCATING_TEXTURE_SECTIONS); + NumJettisonedSections = 0; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + MemorySafetyMargin = 0; + AmountJettisoned = 0; + bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); + AssignLoadingPriority(); + CalculateLoadingBacklog(); } void TrackStreamer::HandleLoading() { @@ -2113,376 +2278,189 @@ void TrackStreamer::CalculateLoadingBacklog() { LoadingBacklog = loading_backlog; } -bool TrackStreamer::AreAllSectionsActivated() { - bool all_sections_activated = false; - if (LoadingPhase == LOADING_IDLE) { - all_sections_activated = NumCurrentStreamingSections <= NumSectionsActivated + NumSectionsOutOfMemory; - } - return all_sections_activated; -} - -bool TrackStreamer::IsLoadingInProgress() { - bool loading_in_progress = !AreAllSectionsActivated(); - - if (!loading_in_progress && !AreAllSectionsActivated()) { - while (!AreAllSectionsActivated()) { - ServiceResourceLoading(); - ServiceNonGameState(); - } - } - - return loading_in_progress; -} - -bool TrackStreamer::CheckLoadingBar() { - ProfileNode profile_node("TODO", 0); - float closest_distance = kMaxDistance_TrackStreamer; - TrackStreamingSection *closest_section; - StreamingPositionEntry *closest_position_entry; - float closest_approach_speed; - bool need_loading_bar; - - for (int position_number = 0; position_number < 2; position_number++) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - float speed; - float max_speed; - float prediction_scale_a = kPredictionScaleA_TrackStreamer; - float prediction_scale_b = kPredictionScaleB_TrackStreamer; - - if (!IsLoadingInProgress()) { - break; - } - - if (IsFarLoadingInProgress() || !position_entry->PositionSet || !position_entry->FollowingCar) { - break; - } - - speed = bLength(&position_entry->Velocity); - max_speed = MPH2MPS(kLoadingBarSpeedThreshold_TrackStreamer); - if (speed > max_speed) { - break; - } - - for (int n = 0; n < NumCurrentStreamingSections; n++) { - TrackStreamingSection *section = CurrentStreamingSections[n]; - VisibleSectionBoundary *boundary = section->pBoundary; - - if (boundary) { - bool may_contain_road = false; - if (IsRegularScenerySection(section->SectionNumber)) { - if (IsScenerySectionDrivable(section->SectionNumber) || IsLODScenerySectionNumber(section->SectionNumber)) { - may_contain_road = true; - } - } - - if (may_contain_road && section->Status != TrackStreamingSection::ACTIVATED) { - const float small_test_time = kFuturePositionScale_TrackStreamer; - bVector2 test_pos = position_entry->Position + position_entry->Velocity * small_test_time; - float distance1 = boundary->GetDistanceOutside(&position_entry->Position, kMaxDistance_TrackStreamer); - float distance2 = boundary->GetDistanceOutside(&test_pos, kMaxDistance_TrackStreamer); - float approach_speed = (distance1 - distance2) * prediction_scale_a * prediction_scale_b; - float distance = distance1 - approach_speed; - if (distance < closest_distance) { - closest_distance = distance; - closest_section = section; - closest_position_entry = position_entry; - closest_approach_speed = approach_speed; - } - } - } - } - } - - need_loading_bar = closest_distance < kLoadingBarDistanceThreshold_TrackStreamer; - prev_need_loading_bar_26275 = need_loading_bar; - return need_loading_bar; -} - -void *TrackStreamer::AllocateUserMemory(int size, const char *debug_name, int offset) { -#ifndef MILESTONE_OPT - (void)debug_name; -#endif - - int allocation_params; - if (size > bLargestMalloc(7)) { - allocation_params = (offset & 0x1FFC) << 17 | 0x2000; - } else { - allocation_params = (offset & 0x1FFC) << 17 | 0x2047; - } -#ifdef MILESTONE_OPT - return bMalloc(size, debug_name, 0, allocation_params); -#else - return bMalloc(size, allocation_params); -#endif -} - -void TrackStreamer::FreeUserMemory(void *mem) { - int free_before = pMemoryPool->GetAmountFree(); - bFree(mem); - int size = pMemoryPool->GetAmountFree(); - (void)free_before; - (void)size; -} - -bool TrackStreamer::IsUserMemory(void *mem) { - int pos = static_cast(mem) - static_cast(pMemoryPoolMem); - return pMemoryPoolMem && pos >= 0 && pos < MemoryPoolSize; -} - -bool TrackStreamer::MakeSpaceInPool(int size, bool force_unloading) { - WaitForCurrentLoadingToComplete(); - while (bCountFreeMemory(7) < size) { - int amount_unloaded = UnloadLeastRecentlyUsedSection(); - if (amount_unloaded == 0 && (!force_unloading || !JettisonLeastImportantSection())) { - break; - } - } - - ForceHoleFillerMethod = 0; - DoHoleFilling(0x7FFFFFFF); - ForceHoleFillerMethod = -1; - return size <= bLargestMalloc(7); -} - -void TrackStreamer::MakeSpaceInPool(int size, void (*callback)(int), int param) { - if (LoadingPhase == LOADING_IDLE) { - IsLoadingInProgress(); - } - - if (!IsLoadingInProgress()) { - MakeSpaceInPool(size, true); - callback(param); - } else { - MakeSpaceInPoolSize = size; - MakeSpaceInPoolCallback = callback; - MakeSpaceInPoolCallbackParam = param; - pCallback = ReadyToMakeSpaceInPoolBridge; - CallbackParam = reinterpret_cast(this); - } -} - -void TrackStreamer::ReadyToMakeSpaceInPool() { - MakeSpaceInPool(MakeSpaceInPoolSize, true); - - void (*callback)(int) = MakeSpaceInPoolCallback; - int param = MakeSpaceInPoolCallbackParam; - MakeSpaceInPoolCallback = 0; - MakeSpaceInPoolCallbackParam = 0; - MakeSpaceInPoolSize = 0; - callback(param); -} - -void TrackStreamer::ReadyToMakeSpaceInPoolBridge(int param) { - reinterpret_cast(param)->ReadyToMakeSpaceInPool(); -} - -short TrackStreamer::GetPredictedZone(StreamingPositionEntry *position_entry) { - float speed = bLength(&position_entry->Velocity); - int predicted_zone = 0; - bool found_predicted_zone = false; - TrackPathZone *zone = 0; - bVector2 predict_position; - - while ((zone = TheTrackPathManager.FindZone(&position_entry->Position, TRACK_PATH_ZONE_STREAMER_PREDICTION, zone))) { - float elevation = zone->GetElevation(); - if ((0.0f < elevation && position_entry->Elevation < elevation) || (elevation < 0.0f && -elevation < position_entry->Elevation)) { - continue; - } - - float max_speed = kPredictedZoneStopProjectSpeed_TrackStreamer * kPredictedZoneScale_TrackStreamer; - float distance = speed * kPredictedZoneScale_TrackStreamer; - DrivableScenerySection *scenery_section; - if (max_speed < speed) { - predict_position = position_entry->Position; - } else if (kPredictedZoneMaxDistance_TrackStreamer < distance) { - bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneMaxDistance_TrackStreamer / speed); - } else { - bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneScale_TrackStreamer); - } - - scenery_section = TheVisibleSectionManager.FindDrivableSection(&predict_position); - if (scenery_section && zone->Data[0] != 0) { - short section_number = scenery_section->SectionNumber; - for (int i = 0; i <= 3; i++) { - if (zone->Data[i] == 0) { - break; +void TrackStreamer::StartLoadingSections() { + bool something_to_load = true; + while (NumSectionsLoading < 2 && something_to_load) { + int best_priority = 0x7FFFFFFF; + TrackStreamingSection *best_section = 0; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->Status == TrackStreamingSection::ALLOCATED) { + int priority = section->LoadingPriority; + if (section->pDiscBundle) { + priority = -1; } - if (zone->Data[i] == section_number) { - found_predicted_zone = true; - predicted_zone = section_number; - break; + if (priority < best_priority) { + best_priority = priority; + best_section = section; } + } else if (section->Status == TrackStreamingSection::LOADED && !NeedsGameStateActivation(section)) { + TheTrackStreamer.ActivateSection(section); } } - } - if (found_predicted_zone) { - if (!bEqual(&predict_position, &position_entry->Position, kPredictedZoneEqualEpsilon_TrackStreamer)) { - for (int barrier_num = 0; barrier_num < NumBarriers; barrier_num++) { - TrackStreamingBarrier *barrier = &pBarriers[barrier_num]; - if (barrier->Intersects(&position_entry->Position, &predict_position)) { - found_predicted_zone = false; - predicted_zone = 0; - } + if (!best_section) { + something_to_load = false; + } else { + if (best_section->pDiscBundle) { + DiscBundleSection *disc_bundle = best_section->pDiscBundle; + LoadDiscBundle(disc_bundle); + } else { + LoadSection(best_section); } } - - if (found_predicted_zone) { - return predicted_zone; - } } +} - DrivableScenerySection *scenery_section = TheVisibleSectionManager.FindDrivableSection(&position_entry->Position); - if (scenery_section) { - predicted_zone = scenery_section->SectionNumber; +void TrackStreamer::FinishedLoading() { + { + float load_time; + int position_number; + StreamingPositionEntry *position_entry; + (void)load_time; + (void)position_number; + (void)position_entry; } - return predicted_zone; -} -void TrackStreamer::ClearStreamingPositions() { + LoadingPhase = LOADING_IDLE; + CurrentZoneNonReplayLoad = false; + CurrentZoneFarLoad = false; + NotifySkyLoader(); + for (int position_number = 0; position_number < 2; position_number++) { StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - position_entry->PositionSet = false; - position_entry->FollowingCar = false; + if (position_entry->BeginLoadingTime != 0.0f) { + PlotLoadingMarker(position_entry); + } + position_entry->BeginLoadingTime = 0.0f; } -} -void TrackStreamer::SetStreamingPosition(int position_number, const bVector3 *position) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - position_entry->Position.x = position->x; - position_entry->Position.y = position->y; - position_entry->PredictedZone = 0; - position_entry->Elevation = position->z; - position_entry->Direction.y = 0.0f; - position_entry->PredictedZoneValidTime = -1; - position_entry->Velocity.x = 0.0f; - position_entry->Velocity.y = 0.0f; - position_entry->Direction.x = 0.0f; - position_entry->PositionSet = true; - position_entry->FollowingCar = false; - CurrentZoneNeedsRefreshing = true; + if (pCallback) { + SetDelayedResourceCallback(pCallback, CallbackParam); + pCallback = 0; + CallbackParam = 0; + } } -void TrackStreamer::PredictStreamingPosition(int position_number, const bVector3 *position, const bVector3 *velocity, const bVector3 *direction, - bool following_car) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - position_entry->Position.x = position->x; - position_entry->Position.y = position->y; - position_entry->Elevation = position->z; - position_entry->Velocity.x = velocity->x; - position_entry->Velocity.y = velocity->y; - position_entry->Direction.x = direction->x; - float direction_y = direction->y; - position_entry->FollowingCar = following_car; - position_entry->Direction.y = direction_y; - position_entry->PositionSet = true; +void TrackStreamer::PlotLoadingMarker(StreamingPositionEntry *streaming_position) { + char stack[0x20]; + (void)stack; } -bool TrackStreamer::DetermineCurrentZones(short *current_zones) { - bool changed = false; +bool TrackStreamer::CheckLoadingBar() { + ProfileNode profile_node("TODO", 0); + float closest_distance = kMaxDistance_TrackStreamer; + TrackStreamingSection *closest_section; + StreamingPositionEntry *closest_position_entry; + float closest_approach_speed; + bool need_loading_bar; + for (int position_number = 0; position_number < 2; position_number++) { StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - short current_zone = -1; - if (position_entry->PositionSet) { - current_zone = GetPredictedZone(position_entry); - } + float speed; + float max_speed; + float prediction_scale_a = kPredictionScaleA_TrackStreamer; + float prediction_scale_b = kPredictionScaleB_TrackStreamer; - if (current_zone == position_entry->PredictedZone) { - position_entry->PredictedZoneValidTime += 1; - } else if (position_entry->PredictedZoneValidTime == -1) { - position_entry->PredictedZone = current_zone; - position_entry->PredictedZoneValidTime = 1000; - } else { - position_entry->PredictedZone = current_zone; - position_entry->PredictedZoneValidTime = 1; + if (!IsLoadingInProgress()) { + break; } - short section_number = position_entry->CurrentZone; - if (current_zone != section_number) { - if (position_entry->PredictedZoneValidTime < 0) { - current_zone = section_number; - } - if (current_zone != section_number) { - changed = true; - } + if (IsFarLoadingInProgress() || !position_entry->PositionSet || !position_entry->FollowingCar) { + break; } - current_zones[position_number] = current_zone; - } + speed = bLength(&position_entry->Velocity); + max_speed = MPH2MPS(kLoadingBarSpeedThreshold_TrackStreamer); + if (speed > max_speed) { + break; + } - return changed || CurrentZoneNeedsRefreshing; -} + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + VisibleSectionBoundary *boundary = section->pBoundary; -void TrackStreamer::ServiceGameState() { - float start_time = GetDebugRealTime(); - HandleZoneSwitching(); - HandleSectionActivation(); - float time = GetDebugRealTime(); - (void)start_time; - (void)time; + if (boundary) { + bool may_contain_road = false; + if (IsRegularScenerySection(section->SectionNumber)) { + if (IsScenerySectionDrivable(section->SectionNumber) || IsLODScenerySectionNumber(section->SectionNumber)) { + may_contain_road = true; + } + } - AmountNotRendered = 0; - for (int n = 0; n < NumCurrentStreamingSections; n++) { - TrackStreamingSection *section = CurrentStreamingSections[n]; - if (!section->WasRendered && IsRegularScenerySection(section->SectionNumber)) { - AmountNotRendered += section->Size; + if (may_contain_road && section->Status != TrackStreamingSection::ACTIVATED) { + const float small_test_time = kFuturePositionScale_TrackStreamer; + bVector2 test_pos = position_entry->Position + position_entry->Velocity * small_test_time; + float distance1 = boundary->GetDistanceOutside(&position_entry->Position, kMaxDistance_TrackStreamer); + float distance2 = boundary->GetDistanceOutside(&test_pos, kMaxDistance_TrackStreamer); + float approach_speed = (distance1 - distance2) * prediction_scale_a * prediction_scale_b; + float distance = distance1 - approach_speed; + if (distance < closest_distance) { + closest_distance = distance; + closest_section = section; + closest_position_entry = position_entry; + closest_approach_speed = approach_speed; + } + } + } } - section->WasRendered = 0; } -} -void TrackStreamer::ServiceNonGameState() { - ProfileNode profile_node("TODO", 0); - float start_time = GetDebugRealTime(); - HandleLoading(); - float time = GetDebugRealTime(); - (void)start_time; - (void)time; + need_loading_bar = closest_distance < kLoadingBarDistanceThreshold_TrackStreamer; + prev_need_loading_bar_26275 = need_loading_bar; + return need_loading_bar; } -void TrackStreamer::SetLoadingPhase(eLoadingPhase phase) { - LoadingPhase = phase; - if (phase == LOADING_IDLE || phase == LOADING_REGULAR_SECTIONS) { - SetQueuedFileMinPriority(0); - } else { - SetQueuedFileMinPriority(QueuedFileDefaultPriority); +int TrackStreamer::GetSectionToActivate(int activation_delay) { + if (NumSectionsActivated < NumCurrentStreamingSections) { + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + if (section->Status == TrackStreamingSection::LOADED && TheTrackStreamer.NeedsGameStateActivation(section) && + RealTimeFrames - section->LoadedTime >= activation_delay) { + return section->SectionNumber; + } + } } -} -void TrackStreamer::BlockUntilLoadingComplete() { - RefreshLoading(); - WaitForCurrentLoadingToComplete(); + return 0; } -void TrackStreamer::WaitForCurrentLoadingToComplete() { - while (!AreAllSectionsActivated()) { - HandleLoading(); - short section_to_activate = static_cast(GetSectionToActivate(0)); - if (section_to_activate != 0) { - ActivateSection(FindSection(section_to_activate)); +void TrackStreamer::HandleSectionActivation() { + ProfileNode profile_node("TODO", 0); + int activation_delay; + short section_to_activate = static_cast(GetSectionToActivate(0)); + (void)activation_delay; + if (section_to_activate != 0) { + TrackStreamingSection *section = FindSection(section_to_activate); + if (section->Status != TrackStreamingSection::ACTIVATED) { + if (section->Status != TrackStreamingSection::LOADED) { + if (!section->CurrentlyVisible) { + return; + } + + do { + HandleLoading(); + ServiceResourceLoading(); + } while (section->Status != TrackStreamingSection::LOADED); + } + ActivateSection(section); } - ServiceResourceLoading(); - bThreadYield(8); } } -void TrackStreamer::RefreshLoading() { - CurrentZoneNeedsRefreshing = true; - for (int position_number = 0; position_number < 2; position_number++) { - StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - position_entry->PredictedZoneValidTime = -1; +void TrackStreamer::UnloadEverything() { + while (NumSectionsLoading != 0) { + ServiceResourceLoading(); } - HandleZoneSwitching(); -} -void TrackStreamer::HandleZoneSwitching() { - ProfileNode profile_node("TODO", 0); - short current_zones[2]; - bool current_zones_different; - if (!ZoneSwitchingDisabled && pMemoryPoolMem) { - current_zones_different = DetermineCurrentZones(current_zones); - if (current_zones_different) { - SwitchZones(current_zones); + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (static_cast(section->Status - TrackStreamingSection::LOADED) < 2U) { + UnloadSection(section); } } + + FreeSectionMemory(); + ClearCurrentZones(); } + diff --git a/src/Speed/Indep/Src/World/VisibleSection.cpp b/src/Speed/Indep/Src/World/VisibleSection.cpp index 8d8aef650..8923f63de 100644 --- a/src/Speed/Indep/Src/World/VisibleSection.cpp +++ b/src/Speed/Indep/Src/World/VisibleSection.cpp @@ -4,6 +4,90 @@ #include "Speed/Indep/bWare/Inc/Strings.hpp" #include "Speed/Indep/bWare/Inc/bWare.hpp" +void RefreshTrackStreamer(); +BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); +float bDistToLine(const bVector2 *point, const bVector2 *pointa, const bVector2 *pointb); + +struct SectionRemapper { + short SectionNumber; + short SectionNumber2P; +}; + +extern SectionRemapper SectionRemapperTable_Gamecube[129]; +extern SectionRemapper SectionRemapperTable[134]; +extern VisibleSectionManager TheVisibleSectionManager; + +static bool initialized_VisibleSection = false; +static int map_table_VisibleSection[2800]; +static int counter_VisibleSection = 0; +static char text_VisibleSection[4][16]; + +int Get2PlayerSectionNumber(int section_number, const char *build_platform) { + if (bStrICmp(build_platform, "PC") != 0) { + char section_letter = GetScenerySectionLetter(section_number); + if (section_letter == 'Y') { + return static_cast(section_number % 100 + 0x8FC); + } + + if (section_letter == 'X') { + return static_cast(section_number % 100 + 0x834); + } + + SectionRemapper *remap_table = SectionRemapperTable; + int table_size = 134; + if (bStrICmp(build_platform, "GAMECUBE") == 0) { + table_size = 129; + remap_table = SectionRemapperTable_Gamecube; + } + + for (int n = 0; n < table_size; n++) { + if (remap_table[n].SectionNumber == section_number) { + return remap_table[n].SectionNumber2P; + } + } + } + + return section_number; +} + +int Get2PlayerSectionNumber(int section_number) { + return Get2PlayerSectionNumber(section_number, bGetPlatformName()); +} + +int Get1PlayerSectionNumber(int section_number_2p, const char *build_platform) { + if (!initialized_VisibleSection) { + initialized_VisibleSection = true; + bMemSet(map_table_VisibleSection, 0, sizeof(map_table_VisibleSection)); + for (int sec_1p = 0; sec_1p < 2800; sec_1p++) { + int sec_2p = Get2PlayerSectionNumber(sec_1p, build_platform); + if (sec_2p != sec_1p) { + map_table_VisibleSection[sec_2p] = sec_1p; + } + } + } + + if (map_table_VisibleSection[section_number_2p] != 0) { + return map_table_VisibleSection[section_number_2p]; + } + return section_number_2p; +} + +int GetBoundarySectionNumber(int section_number, const char *platform_name) { + int boundary_section_number = Get1PlayerSectionNumber(section_number, platform_name); + int subsection_number = boundary_section_number % 100; + int is_boundary_section = 0; + + if (subsection_number >= ScenerySectionLODOffset) { + is_boundary_section = subsection_number < ScenerySectionLODOffset * 2; + } + + if (is_boundary_section) { + boundary_section_number -= ScenerySectionLODOffset; + } + + return boundary_section_number; +} + int LoaderVisibleSections(bChunk *chunk) { return TheVisibleSectionManager.Loader(chunk); } @@ -12,19 +96,206 @@ int UnloaderVisibleSections(bChunk *chunk) { return TheVisibleSectionManager.Unloader(chunk); } -void RefreshTrackStreamer(); -BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); -int Get2PlayerSectionNumber(int section_number, const char *build_platform); -int Get2PlayerSectionNumber(int section_number); -int Get1PlayerSectionNumber(int section_number_2p, const char *build_platform); -int GetBoundarySectionNumber(int section_number, const char *platform_name); -static bool MyIsPointInPoly(const bVector2 *point, const bVector2 *polygon, int num_points); -float bDistToLine(const bVector2 *point, const bVector2 *pointa, const bVector2 *pointb); +char *GetScenerySectionName(char *name, int section_number) { + if (section_number < 1) { + name[0] = '-'; + name[1] = '-'; + name[2] = '\0'; + } else { + bSPrintf(name, "%c%d", GetScenerySectionLetter(section_number), section_number % 100); + } -struct SectionRemapper { - short SectionNumber; - short SectionNumber2P; -}; + return name; +} + +char *GetScenerySectionName(int section_number) { + unsigned int index = static_cast(counter_VisibleSection) & 3; + counter_VisibleSection += 1; + return GetScenerySectionName(text_VisibleSection[index], section_number); +} + +static bool MyIsPointInPoly(const bVector2 *point, const bVector2 *points, int num_points) { + float x = point->x; + float y = point->y; + bool inside = false; + int j = num_points - 1; + + for (int i = 0; i < num_points; i++) { + float point_y = points[i].y; + if (((point_y <= y && y < points[j].y) || (points[j].y <= y && y < point_y)) && + x < ((points[j].x - points[i].x) * (y - point_y)) / (points[j].y - point_y) + points[i].x) { + inside = !inside; + } + + j = i; + } + + return inside; +} + +static inline bool PointInBBox(const bVector2 *point, const bVector2 *bbox_min, const bVector2 *bbox_max) { + return point->x >= bbox_min->x && point->x <= bbox_max->x && point->y >= bbox_min->y && point->y <= bbox_max->y; +} + +bool VisibleSectionBoundary::IsPointInside(const bVector2 *point) { + if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, 0.0f)) { + return false; + } + + return MyIsPointInPoly(point, Points, NumPoints); +} + +float VisibleSectionBoundary::GetDistanceOutside(const bVector2 *point, float max_distance) { + if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, max_distance)) { + return max_distance; + } + + if (IsPointInside(point)) { + return 0.0f; + } + + float closest_distance = max_distance; + { + int point_number = 0; + while (point_number < NumPoints) { + int next = point_number + 1; + bVector2 *point1 = GetPoint(point_number); + bVector2 *point2 = GetPoint(next - (next / NumPoints) * NumPoints); + float distance = bDistToLine(point, point1, point2); + if (distance < closest_distance) { + closest_distance = distance; + } + point_number = next; + } + } + + return closest_distance; +} + +void DrivableScenerySection::AddVisibleSection(int section_number) { + if (NumVisibleSections < MaxVisibleSections && !IsSectionVisible(section_number)) { + short num_visible_sections = NumVisibleSections; + NumVisibleSections = num_visible_sections + 1; + VisibleSections[num_visible_sections] = static_cast(section_number); + if (MostVisibleSections < NumVisibleSections) { + MostVisibleSections = NumVisibleSections; + } + } +} + +int DrivableScenerySection::IsSectionVisible(int section_number) { + for (int i = 0; i < NumVisibleSections; i++) { + if (VisibleSections[i] == section_number) { + return 1; + } + } + return 0; +} + +void DrivableScenerySection::RemoveVisibleSection(int section_number) { + { + int n = 0; + if (n >= NumVisibleSections) { + return; + } + + do { + if (VisibleSections[n] == section_number) { + { + int i = n; + + if (i < NumVisibleSections - 1) { + do { + VisibleSections[i] = VisibleSections[i + 1]; + i++; + } while (i < NumVisibleSections - 1); + } + } + + NumVisibleSections--; + VisibleSections[NumVisibleSections] = 0; + return; + } + + n++; + } while (n < NumVisibleSections); + } +} + +void DrivableScenerySection::SortVisibleSections() { + bool swap; + do { + swap = false; + if (NumVisibleSections - 1 > 0) { + for (int i = 0; i < NumVisibleSections - 1; i++) { + short a = VisibleSections[i]; + short b = VisibleSections[i + 1]; + if (b < a) { + VisibleSections[i + 1] = a; + swap = true; + VisibleSections[i] = b; + } + } + } + } while (swap); +} + +LoadingSection *VisibleSectionManager::FindLoadingSection(int section_number) { + for (LoadingSection *loading_section = LoadingSectionList.GetHead(); loading_section != LoadingSectionList.EndOfList(); + loading_section = loading_section->GetNext()) { + short target_section_number = static_cast(section_number); + short *drivable_sections = loading_section->DrivableSections; + int num_drivable_sections = loading_section->NumDrivableSections; + + for (int i = 0; i < num_drivable_sections; i++) { + if (drivable_sections[i] == target_section_number) { + return loading_section; + } + } + } + + return 0; +} + +int VisibleSectionManager::GetSectionsToLoad(LoadingSection *loading_section, short *section_numbers, int max_sections) { + if (!loading_section) { + return 0; + } + + int num_sections = 0; + for (int n = 0; n < loading_section->NumDrivableSections; n++) { + DrivableScenerySection *drivable_section = FindDrivableSection(loading_section->DrivableSections[n]); + if (!drivable_section) { + continue; + } + + for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { + int section_number = drivable_section->GetVisibleSection(i); + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + } + } + + for (int i = 0; i < loading_section->NumExtraSections; i++) { + int section_number = loading_section->ExtraSections[i]; + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + + if (IsScenerySectionDrivable(section_number)) { + section_number = GetLODScenerySectionNumber(section_number); + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + } + } + + return num_sections; +} VisibleGroupInfo VisibleGroupInfoTable[5] = { {"BARRIER_", 1}, @@ -280,186 +551,56 @@ SectionRemapper SectionRemapperTable[134] = { {GetScenerySectionNumber('H', 15), GetScenerySectionNumber('H', 34)}, {GetScenerySectionNumber('H', 55), GetScenerySectionNumber('H', 74)}, {GetScenerySectionNumber('G', 17), GetScenerySectionNumber('G', 35)}, - {GetScenerySectionNumber('G', 57), GetScenerySectionNumber('G', 75)}, - {GetScenerySectionNumber('V', 14), GetScenerySectionNumber('V', 99)}, - {GetScenerySectionNumber('V', 4), GetScenerySectionNumber('V', 98)}, - {GetScenerySectionNumber('V', 41), GetScenerySectionNumber('V', 97)}, - {GetScenerySectionNumber('V', 31), GetScenerySectionNumber('V', 96)}, - {GetScenerySectionNumber('V', 55), GetScenerySectionNumber('V', 95)}, - {GetScenerySectionNumber('E', 2), GetScenerySectionNumber('E', 39)}, - {GetScenerySectionNumber('E', 42), GetScenerySectionNumber('E', 79)}, - {GetScenerySectionNumber('E', 3), GetScenerySectionNumber('E', 38)}, - {GetScenerySectionNumber('E', 43), GetScenerySectionNumber('E', 78)}, - {GetScenerySectionNumber('E', 5), GetScenerySectionNumber('E', 37)}, - {GetScenerySectionNumber('E', 45), GetScenerySectionNumber('E', 77)}, - {GetScenerySectionNumber('E', 8), GetScenerySectionNumber('E', 36)}, - {GetScenerySectionNumber('E', 48), GetScenerySectionNumber('E', 76)}, - {GetScenerySectionNumber('E', 1), GetScenerySectionNumber('E', 35)}, - {GetScenerySectionNumber('E', 41), GetScenerySectionNumber('E', 75)}, - {GetScenerySectionNumber('M', 12), GetScenerySectionNumber('M', 39)}, - {GetScenerySectionNumber('M', 52), GetScenerySectionNumber('M', 79)}, - {GetScenerySectionNumber('C', 9), GetScenerySectionNumber('C', 39)}, -}; - -VisibleSectionManager TheVisibleSectionManager; // size: 0x6830 -int ScenerySectionLODOffset = 0; -DrivableScenerySection *pSectionD9 = 0; -DrivableScenerySection *pSectionC14 = 0; -char *bGetPlatformName(); -int bStrNICmp(const char *s1, const char *s2, int n); - -static bool initialized_VisibleSection = false; -static int map_table_VisibleSection[2800]; -static int counter_VisibleSection = 0; -static char text_VisibleSection[4][16]; - -VisibleSectionManager::VisibleSectionManager() { - pBoundaryChunks = 0; - pInfo = 0; - pActiveOverlay = 0; - pUndoOverlay = 0; - - bMemSet(UserInfoTable, 0, sizeof(UserInfoTable)); - NumAllocatedUserInfo = 0; - - bNode *head = &UnallocatedUserInfoList.HeadNode; - for (int i = 0; i < 512; i++) { - VisibleSectionUserInfo *user_info = &UserInfoStorageTable[i]; - bNode *tail = head->Prev; - - tail->Next = reinterpret_cast(user_info); - head->Prev = reinterpret_cast(user_info); - reinterpret_cast(user_info)->Prev = tail; - reinterpret_cast(user_info)->Next = head; - } - - VisibleBitTables = 0; - bMemSet(EnabledGroups, 0, sizeof(EnabledGroups)); -} - -char *GetScenerySectionName(char *name, int section_number) { - if (section_number < 1) { - name[0] = '-'; - name[1] = '-'; - name[2] = '\0'; - } else { - bSPrintf(name, "%c%d", GetScenerySectionLetter(section_number), section_number % 100); - } - - return name; -} - -char *GetScenerySectionName(int section_number) { - unsigned int index = static_cast(counter_VisibleSection) & 3; - counter_VisibleSection += 1; - return GetScenerySectionName(text_VisibleSection[index], section_number); -} - -static inline bool PointInBBox(const bVector2 *point, const bVector2 *bbox_min, const bVector2 *bbox_max) { - return point->x >= bbox_min->x && point->x <= bbox_max->x && point->y >= bbox_min->y && point->y <= bbox_max->y; -} - -bool VisibleSectionBoundary::IsPointInside(const bVector2 *point) { - if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, 0.0f)) { - return false; - } - - return MyIsPointInPoly(point, Points, NumPoints); -} - -float VisibleSectionBoundary::GetDistanceOutside(const bVector2 *point, float max_distance) { - if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, max_distance)) { - return max_distance; - } - - if (IsPointInside(point)) { - return 0.0f; - } - - float closest_distance = max_distance; - { - int point_number = 0; - while (point_number < NumPoints) { - int next = point_number + 1; - bVector2 *point1 = GetPoint(point_number); - bVector2 *point2 = GetPoint(next - (next / NumPoints) * NumPoints); - float distance = bDistToLine(point, point1, point2); - if (distance < closest_distance) { - closest_distance = distance; - } - point_number = next; - } - } - - return closest_distance; -} - -void DrivableScenerySection::AddVisibleSection(int section_number) { - if (NumVisibleSections < MaxVisibleSections && !IsSectionVisible(section_number)) { - short num_visible_sections = NumVisibleSections; - NumVisibleSections = num_visible_sections + 1; - VisibleSections[num_visible_sections] = static_cast(section_number); - if (MostVisibleSections < NumVisibleSections) { - MostVisibleSections = NumVisibleSections; - } - } -} - -int DrivableScenerySection::IsSectionVisible(int section_number) { - for (int i = 0; i < NumVisibleSections; i++) { - if (VisibleSections[i] == section_number) { - return 1; - } - } - return 0; -} + {GetScenerySectionNumber('G', 57), GetScenerySectionNumber('G', 75)}, + {GetScenerySectionNumber('V', 14), GetScenerySectionNumber('V', 99)}, + {GetScenerySectionNumber('V', 4), GetScenerySectionNumber('V', 98)}, + {GetScenerySectionNumber('V', 41), GetScenerySectionNumber('V', 97)}, + {GetScenerySectionNumber('V', 31), GetScenerySectionNumber('V', 96)}, + {GetScenerySectionNumber('V', 55), GetScenerySectionNumber('V', 95)}, + {GetScenerySectionNumber('E', 2), GetScenerySectionNumber('E', 39)}, + {GetScenerySectionNumber('E', 42), GetScenerySectionNumber('E', 79)}, + {GetScenerySectionNumber('E', 3), GetScenerySectionNumber('E', 38)}, + {GetScenerySectionNumber('E', 43), GetScenerySectionNumber('E', 78)}, + {GetScenerySectionNumber('E', 5), GetScenerySectionNumber('E', 37)}, + {GetScenerySectionNumber('E', 45), GetScenerySectionNumber('E', 77)}, + {GetScenerySectionNumber('E', 8), GetScenerySectionNumber('E', 36)}, + {GetScenerySectionNumber('E', 48), GetScenerySectionNumber('E', 76)}, + {GetScenerySectionNumber('E', 1), GetScenerySectionNumber('E', 35)}, + {GetScenerySectionNumber('E', 41), GetScenerySectionNumber('E', 75)}, + {GetScenerySectionNumber('M', 12), GetScenerySectionNumber('M', 39)}, + {GetScenerySectionNumber('M', 52), GetScenerySectionNumber('M', 79)}, + {GetScenerySectionNumber('C', 9), GetScenerySectionNumber('C', 39)}, +}; -void DrivableScenerySection::RemoveVisibleSection(int section_number) { - { - int n = 0; - if (n >= NumVisibleSections) { - return; - } +VisibleSectionManager TheVisibleSectionManager; // size: 0x6830 +int ScenerySectionLODOffset = 0; +DrivableScenerySection *pSectionD9 = 0; +DrivableScenerySection *pSectionC14 = 0; +char *bGetPlatformName(); +int bStrNICmp(const char *s1, const char *s2, int n); - do { - if (VisibleSections[n] == section_number) { - { - int i = n; +VisibleSectionManager::VisibleSectionManager() { + pBoundaryChunks = 0; + pInfo = 0; + pActiveOverlay = 0; + pUndoOverlay = 0; - if (i < NumVisibleSections - 1) { - do { - VisibleSections[i] = VisibleSections[i + 1]; - i++; - } while (i < NumVisibleSections - 1); - } - } + bMemSet(UserInfoTable, 0, sizeof(UserInfoTable)); + NumAllocatedUserInfo = 0; - NumVisibleSections--; - VisibleSections[NumVisibleSections] = 0; - return; - } + bNode *head = &UnallocatedUserInfoList.HeadNode; + for (int i = 0; i < 512; i++) { + VisibleSectionUserInfo *user_info = &UserInfoStorageTable[i]; + bNode *tail = head->Prev; - n++; - } while (n < NumVisibleSections); + tail->Next = reinterpret_cast(user_info); + head->Prev = reinterpret_cast(user_info); + reinterpret_cast(user_info)->Prev = tail; + reinterpret_cast(user_info)->Next = head; } -} -void DrivableScenerySection::SortVisibleSections() { - bool swap; - do { - swap = false; - if (NumVisibleSections - 1 > 0) { - for (int i = 0; i < NumVisibleSections - 1; i++) { - short a = VisibleSections[i]; - short b = VisibleSections[i + 1]; - if (b < a) { - VisibleSections[i + 1] = a; - swap = true; - VisibleSections[i] = b; - } - } - } - } while (swap); + VisibleBitTables = 0; + bMemSet(EnabledGroups, 0, sizeof(EnabledGroups)); } VisibleSectionUserInfo *VisibleSectionManager::AllocateUserInfo(int section_number) { @@ -488,181 +629,18 @@ void VisibleSectionManager::UnallocateUserInfo(int section_number) { } int ref_count = user_info->ReferenceCount - 1; - user_info->ReferenceCount = ref_count; - if (ref_count != 0) { - return; - } - - bNode *tail = UnallocatedUserInfoList.HeadNode.Prev; - NumAllocatedUserInfo -= 1; - tail->Next = reinterpret_cast(user_info); - UnallocatedUserInfoList.HeadNode.Prev = reinterpret_cast(user_info); - reinterpret_cast(user_info)->Prev = tail; - reinterpret_cast(user_info)->Next = &UnallocatedUserInfoList.HeadNode; - UserInfoTable[section_number] = 0; -} - -VisibleSectionBoundary *VisibleSectionManager::FindBoundary(int section_number) { - for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); - boundary = boundary->GetNext()) { - if (boundary->SectionNumber == section_number) { - return boundary; - } - } - - for (VisibleSectionBoundary *boundary = NonDrivableBoundaryList.GetHead(); boundary != NonDrivableBoundaryList.EndOfList(); - boundary = boundary->GetNext()) { - if (boundary->SectionNumber == section_number) { - return boundary; - } - } - - return 0; -} - -VisibleSectionBoundary *VisibleSectionManager::FindClosestBoundary(const bVector2 *point, float *distance) { - float closest_distance = 9999999.0f; - VisibleSectionBoundary *closest_boundary = 0; - - for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); - boundary = boundary->GetNext()) { - if (boundary->IsPointInside(point)) { - closest_distance = 0.0f; - DrivableBoundaryList.Remove(boundary); - DrivableBoundaryList.AddHead(boundary); - closest_boundary = boundary; - break; - } - - float boundary_distance = boundary->GetDistanceOutside(point, closest_distance); - if (!closest_boundary || boundary_distance < closest_distance || - (boundary_distance == closest_distance && boundary->SectionNumber < closest_boundary->SectionNumber)) { - closest_distance = boundary_distance; - closest_boundary = boundary; - } - } - - if (distance) { - *distance = closest_distance; - } - - return closest_boundary; -} - -VisibleSectionBoundary *VisibleSectionManager::FindBoundary(const bVector2 *point) { - for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); - boundary = boundary->GetNext()) { - if (boundary->IsPointInside(point)) { - DrivableBoundaryList.Remove(boundary); - DrivableBoundaryList.AddHead(boundary); - return boundary; - } - } - - float distance_to_boundary; - VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance_to_boundary); - if (distance_to_boundary >= 0.1f) { - return 0; - } - - return boundary; -} - -DrivableScenerySection *VisibleSectionManager::FindDrivableSection(const bVector2 *point) { - for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { - if (section->pBoundary->IsPointInside(point)) { - DrivableSectionList.Remove(section); - DrivableSectionList.AddHead(section); - return section; - } - } - - float distance; - VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance); - if (distance < 0.1f) { - return FindDrivableSection(boundary->SectionNumber); - } - - return 0; -} - -DrivableScenerySection *VisibleSectionManager::FindDrivableSection(int section_number) { - for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { - if (section->SectionNumber == section_number) { - return section; - } - } - - return 0; -} - -LoadingSection *VisibleSectionManager::FindLoadingSection(int section_number) { - for (LoadingSection *loading_section = LoadingSectionList.GetHead(); loading_section != LoadingSectionList.EndOfList(); - loading_section = loading_section->GetNext()) { - short target_section_number = static_cast(section_number); - short *drivable_sections = loading_section->DrivableSections; - int num_drivable_sections = loading_section->NumDrivableSections; - - for (int i = 0; i < num_drivable_sections; i++) { - if (drivable_sections[i] == target_section_number) { - return loading_section; - } - } - } - - return 0; -} - -int VisibleSectionManager::GetSectionsToLoad(LoadingSection *loading_section, short *section_numbers, int max_sections) { - if (!loading_section) { - return 0; - } - - int num_sections = 0; - for (int n = 0; n < loading_section->NumDrivableSections; n++) { - DrivableScenerySection *drivable_section = FindDrivableSection(loading_section->DrivableSections[n]); - if (!drivable_section) { - continue; - } - - for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { - int section_number = drivable_section->GetVisibleSection(i); - if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { - section_numbers[num_sections] = section_number; - num_sections += 1; - } - } - } - - for (int i = 0; i < loading_section->NumExtraSections; i++) { - int section_number = loading_section->ExtraSections[i]; - if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { - section_numbers[num_sections] = section_number; - num_sections += 1; - } - - if (IsScenerySectionDrivable(section_number)) { - section_number = GetLODScenerySectionNumber(section_number); - if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { - section_numbers[num_sections] = section_number; - num_sections += 1; - } - } - } - - return num_sections; -} - -VisibleGroupInfo *VisibleSectionManager::GetGroupInfo(const char *selection_set_name) { - VisibleGroupInfo *group_info = VisibleGroupInfoTable; - for (int i = 0; i < 5; i++) { - int name_length = bStrLen(group_info->SelectionSetName); - if (bStrNICmp(selection_set_name, group_info->SelectionSetName, name_length) == 0) { - return group_info; - } - group_info += 1; + user_info->ReferenceCount = ref_count; + if (ref_count != 0) { + return; } - return 0; + + bNode *tail = UnallocatedUserInfoList.HeadNode.Prev; + NumAllocatedUserInfo -= 1; + tail->Next = reinterpret_cast(user_info); + UnallocatedUserInfoList.HeadNode.Prev = reinterpret_cast(user_info); + reinterpret_cast(user_info)->Prev = tail; + reinterpret_cast(user_info)->Next = &UnallocatedUserInfoList.HeadNode; + UserInfoTable[section_number] = 0; } void VisibleSectionManager::ActivateOverlay(const char *name) { @@ -735,118 +713,6 @@ void VisibleSectionManager::UnactivateOverlay() { } } -void VisibleSectionManager::EnableGroup(unsigned int group_name) { - for (int i = 0; i < 0x100; i++) { - if (EnabledGroups[i] == 0) { - EnabledGroups[i] = group_name; - return; - } - } -} - -int VisibleSectionManager::Unloader(bChunk *chunk) { - if (chunk->GetID() == 0x80034150) { - pInfo = 0; - DrivableBoundaryList.InitList(); - NonDrivableBoundaryList.InitList(); - LoadingSectionList.InitList(); - DrivableSectionList.InitList(); - return 1; - } - - if (chunk->GetID() == 0x34158) { - reinterpret_cast(chunk->GetData())->Remove(); - return 1; - } - - return 0; -} - -int Get2PlayerSectionNumber(int section_number, const char *build_platform) { - if (bStrICmp(build_platform, "PC") != 0) { - char section_letter = GetScenerySectionLetter(section_number); - if (section_letter == 'Y') { - return static_cast(section_number % 100 + 0x8FC); - } - - if (section_letter == 'X') { - return static_cast(section_number % 100 + 0x834); - } - - SectionRemapper *remap_table = SectionRemapperTable; - int table_size = 134; - if (bStrICmp(build_platform, "GAMECUBE") == 0) { - table_size = 129; - remap_table = SectionRemapperTable_Gamecube; - } - - for (int n = 0; n < table_size; n++) { - if (remap_table[n].SectionNumber == section_number) { - return remap_table[n].SectionNumber2P; - } - } - } - - return section_number; -} - -int Get2PlayerSectionNumber(int section_number) { - return Get2PlayerSectionNumber(section_number, bGetPlatformName()); -} - -int Get1PlayerSectionNumber(int section_number_2p, const char *build_platform) { - if (!initialized_VisibleSection) { - initialized_VisibleSection = true; - bMemSet(map_table_VisibleSection, 0, sizeof(map_table_VisibleSection)); - for (int sec_1p = 0; sec_1p < 2800; sec_1p++) { - int sec_2p = Get2PlayerSectionNumber(sec_1p, build_platform); - if (sec_2p != sec_1p) { - map_table_VisibleSection[sec_2p] = sec_1p; - } - } - } - - if (map_table_VisibleSection[section_number_2p] != 0) { - return map_table_VisibleSection[section_number_2p]; - } - return section_number_2p; -} - -int GetBoundarySectionNumber(int section_number, const char *platform_name) { - int boundary_section_number = Get1PlayerSectionNumber(section_number, platform_name); - int subsection_number = boundary_section_number % 100; - int is_boundary_section = 0; - - if (subsection_number >= ScenerySectionLODOffset) { - is_boundary_section = subsection_number < ScenerySectionLODOffset * 2; - } - - if (is_boundary_section) { - boundary_section_number -= ScenerySectionLODOffset; - } - - return boundary_section_number; -} - -static bool MyIsPointInPoly(const bVector2 *point, const bVector2 *points, int num_points) { - float x = point->x; - float y = point->y; - bool inside = false; - int j = num_points - 1; - - for (int i = 0; i < num_points; i++) { - float point_y = points[i].y; - if (((point_y <= y && y < points[j].y) || (points[j].y <= y && y < point_y)) && - x < ((points[j].x - points[i].x) * (y - point_y)) / (points[j].y - point_y) + points[i].x) { - inside = !inside; - } - - j = i; - } - - return inside; -} - int VisibleSectionManager::Loader(bChunk *chunk) { if (chunk->GetID() == 0x80034150) { bChunk *first_chunk = chunk->GetFirstChunk(); @@ -923,3 +789,135 @@ int VisibleSectionManager::Loader(bChunk *chunk) { return 0; } +int VisibleSectionManager::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x80034150) { + pInfo = 0; + DrivableBoundaryList.InitList(); + NonDrivableBoundaryList.InitList(); + LoadingSectionList.InitList(); + DrivableSectionList.InitList(); + return 1; + } + + if (chunk->GetID() == 0x34158) { + reinterpret_cast(chunk->GetData())->Remove(); + return 1; + } + + return 0; +} + +VisibleSectionBoundary *VisibleSectionManager::FindBoundary(int section_number) { + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->SectionNumber == section_number) { + return boundary; + } + } + + for (VisibleSectionBoundary *boundary = NonDrivableBoundaryList.GetHead(); boundary != NonDrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->SectionNumber == section_number) { + return boundary; + } + } + + return 0; +} + +VisibleSectionBoundary *VisibleSectionManager::FindClosestBoundary(const bVector2 *point, float *distance) { + float closest_distance = 9999999.0f; + VisibleSectionBoundary *closest_boundary = 0; + + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->IsPointInside(point)) { + closest_distance = 0.0f; + DrivableBoundaryList.Remove(boundary); + DrivableBoundaryList.AddHead(boundary); + closest_boundary = boundary; + break; + } + + float boundary_distance = boundary->GetDistanceOutside(point, closest_distance); + if (!closest_boundary || boundary_distance < closest_distance || + (boundary_distance == closest_distance && boundary->SectionNumber < closest_boundary->SectionNumber)) { + closest_distance = boundary_distance; + closest_boundary = boundary; + } + } + + if (distance) { + *distance = closest_distance; + } + + return closest_boundary; +} + +VisibleSectionBoundary *VisibleSectionManager::FindBoundary(const bVector2 *point) { + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->IsPointInside(point)) { + DrivableBoundaryList.Remove(boundary); + DrivableBoundaryList.AddHead(boundary); + return boundary; + } + } + + float distance_to_boundary; + VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance_to_boundary); + if (distance_to_boundary >= 0.1f) { + return 0; + } + + return boundary; +} + +DrivableScenerySection *VisibleSectionManager::FindDrivableSection(const bVector2 *point) { + for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { + if (section->pBoundary->IsPointInside(point)) { + DrivableSectionList.Remove(section); + DrivableSectionList.AddHead(section); + return section; + } + } + + float distance; + VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance); + if (distance < 0.1f) { + return FindDrivableSection(boundary->SectionNumber); + } + + return 0; +} + +DrivableScenerySection *VisibleSectionManager::FindDrivableSection(int section_number) { + for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { + if (section->SectionNumber == section_number) { + return section; + } + } + + return 0; +} + +VisibleGroupInfo *VisibleSectionManager::GetGroupInfo(const char *selection_set_name) { + VisibleGroupInfo *group_info = VisibleGroupInfoTable; + for (int i = 0; i < 5; i++) { + int name_length = bStrLen(group_info->SelectionSetName); + if (bStrNICmp(selection_set_name, group_info->SelectionSetName, name_length) == 0) { + return group_info; + } + group_info += 1; + } + return 0; +} + +void VisibleSectionManager::EnableGroup(unsigned int group_name) { + for (int i = 0; i < 0x100; i++) { + if (EnabledGroups[i] == 0) { + EnabledGroups[i] = group_name; + return; + } + } +} diff --git a/src/Speed/Indep/Src/World/WeatherMan.cpp b/src/Speed/Indep/Src/World/WeatherMan.cpp index 4ecc6bc83..61ba74e79 100644 --- a/src/Speed/Indep/Src/World/WeatherMan.cpp +++ b/src/Speed/Indep/Src/World/WeatherMan.cpp @@ -25,6 +25,90 @@ extern float oldDistFogStart_27399; extern float oldDistFogPower_27398; extern unsigned int oldDistFogColour_27397; +int LoaderWeatherMan(bChunk *bchunk) { + if (bchunk->GetID() != 0x34250) { + return 0; + } + + unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); + bEndianSwap32(data + 8); + bEndianSwap32(data + 0xC); + if (*reinterpret_cast(data + 8) == 2) { + int num_regions = *reinterpret_cast(data + 0xC); + unsigned char *region_data = data + 0x10; + for (int i = 0; i < num_regions; i++) { + bEndianSwap32(region_data + 0x84); + bEndianSwap32(region_data + 0x88); + bEndianSwap32(region_data + 0x48); + bEndianSwap32(region_data + 0x4C); + bEndianSwap32(region_data + 0x50); + bEndianSwap32(region_data + 0x54); + bEndianSwap32(region_data + 0x58); + bEndianSwap32(region_data + 0x5C); + bEndianSwap32(region_data + 0x60); + bEndianSwap32(region_data + 0x64); + bEndianSwap32(region_data + 0x68); + bEndianSwap32(region_data + 0x6C); + bEndianSwap32(region_data + 0x70); + bEndianSwap32(region_data + 0x74); + bEndianSwap32(region_data + 0x78); + bEndianSwap32(region_data + 0x8C); + bEndianSwap32(region_data + 0x90); + bEndianSwap32(region_data + 0x94); + AddRegion(reinterpret_cast(region_data)); + region_data += sizeof(GenericRegion); + } + } + + return 1; +} + +int UnloaderWeatherMan(bChunk *bchunk) { + if (bchunk->GetID() != 0x34250) { + return 0; + } + + unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); + int version = *reinterpret_cast(data + 8); + if (version == 2) { + GenericRegion *region = reinterpret_cast(data + 0x10); + int num_regions = *reinterpret_cast(data + 0xC); + for (int i = 0; i < num_regions; i++) { + RemoveRegion(region); + region += 1; + } + } + + return 1; +} + +void AddRegion(GenericRegion *region) { + unsigned int region_type = static_cast(region->Type); + if (region_type == REGION_RAIN && region->Intensity == 0.0f) { + region->Type = REGION_TUNNEL; + region_type = REGION_TUNNEL; + } + + if (region_type < NUM_REGION_TYPES) { + RegionLists[region_type].AddTail(region); + RegionCount[region_type] += 1; + } +} + +void RemoveRegion(GenericRegion *region) { + region->Remove(); +} + +int DepthRegion(GenericRegion *before, GenericRegion *after) { + bVector3 Position(before->PositionX, before->PositionY, before->PositionZ); + bVector3 Delta = Position - cPos; + float distB = bLength(Delta); + Position = bVector3(after->PositionX, after->PositionY, after->PositionZ); + Delta = Position - cPos; + float distA = bLength(Delta); + return distB <= distA; +} + int RegionQuery::CalculateRegionInfo(eView *view, RegionType regionKind, int InFE) { unsigned int colr_r = 0; unsigned int colr_g = 0; @@ -142,90 +226,6 @@ int RegionQuery::CalculateRegionInfo(eView *view, RegionType regionKind, int InF return 1; } -int LoaderWeatherMan(bChunk *bchunk) { - if (bchunk->GetID() != 0x34250) { - return 0; - } - - unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); - bEndianSwap32(data + 8); - bEndianSwap32(data + 0xC); - if (*reinterpret_cast(data + 8) == 2) { - int num_regions = *reinterpret_cast(data + 0xC); - unsigned char *region_data = data + 0x10; - for (int i = 0; i < num_regions; i++) { - bEndianSwap32(region_data + 0x84); - bEndianSwap32(region_data + 0x88); - bEndianSwap32(region_data + 0x48); - bEndianSwap32(region_data + 0x4C); - bEndianSwap32(region_data + 0x50); - bEndianSwap32(region_data + 0x54); - bEndianSwap32(region_data + 0x58); - bEndianSwap32(region_data + 0x5C); - bEndianSwap32(region_data + 0x60); - bEndianSwap32(region_data + 0x64); - bEndianSwap32(region_data + 0x68); - bEndianSwap32(region_data + 0x6C); - bEndianSwap32(region_data + 0x70); - bEndianSwap32(region_data + 0x74); - bEndianSwap32(region_data + 0x78); - bEndianSwap32(region_data + 0x8C); - bEndianSwap32(region_data + 0x90); - bEndianSwap32(region_data + 0x94); - AddRegion(reinterpret_cast(region_data)); - region_data += sizeof(GenericRegion); - } - } - - return 1; -} - -int UnloaderWeatherMan(bChunk *bchunk) { - if (bchunk->GetID() != 0x34250) { - return 0; - } - - unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); - int version = *reinterpret_cast(data + 8); - if (version == 2) { - GenericRegion *region = reinterpret_cast(data + 0x10); - int num_regions = *reinterpret_cast(data + 0xC); - for (int i = 0; i < num_regions; i++) { - RemoveRegion(region); - region += 1; - } - } - - return 1; -} - -void AddRegion(GenericRegion *region) { - unsigned int region_type = static_cast(region->Type); - if (region_type == REGION_RAIN && region->Intensity == 0.0f) { - region->Type = REGION_TUNNEL; - region_type = REGION_TUNNEL; - } - - if (region_type < NUM_REGION_TYPES) { - RegionLists[region_type].AddTail(region); - RegionCount[region_type] += 1; - } -} - -void RemoveRegion(GenericRegion *region) { - region->Remove(); -} - -int DepthRegion(GenericRegion *before, GenericRegion *after) { - bVector3 Position(before->PositionX, before->PositionY, before->PositionZ); - bVector3 Delta = Position - cPos; - float distB = bLength(Delta); - Position = bVector3(after->PositionX, after->PositionY, after->PositionZ); - Delta = Position - cPos; - float distA = bLength(Delta); - return distB <= distA; -} - GenericRegion *GetClosestRegionInView(eView *view, bVector3 *endVector, float *angleCos) { cPos = *view->GetCamera()->GetPosition(); bVector3 cDir(*view->GetCamera()->GetDirection()); From b9f10c3353f24f489f60e5259b123217e20cb2d1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:56:53 +0100 Subject: [PATCH 173/372] 78.5%: reorder pose anim key walk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 52d5295d5..61316e01e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -40,16 +40,18 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl key = numKeys - 1; } else { key = mPrevKey; - if (times[key] > floorTime) { - if (key != 0) { - do { - key--; - } while (key > 0 && times[key] > floorTime); + if (times[key] <= floorTime) { + if (key < numKeys - 1) { + if (times[key + 1] <= floorTime) { + do { + key++; + } while (key < numKeys - 2 && times[key + 1] <= floorTime); + } } - } else if (times[key + 1] <= floorTime) { + } else if (key != 0) { do { - key++; - } while (key < numKeys - 2 && times[key + 1] <= floorTime); + key--; + } while (key > 0 && times[key] > floorTime); } if (numKeys != 0) { From 5bb4eb4745688afb9674972bd6f11f6f61fc8e63 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:58:41 +0100 Subject: [PATCH 174/372] 78.5%: retime pose blend polynomial Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 61316e01e..0a74ba3a3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -80,7 +80,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl int src1 = (poseAnim->mPoseIndices[key + 1] + 1) * stride - 4; int transIdx = static_cast(numT) - 1; - t = t * (3.0f - (t + t)) * t; + t = (3.0f - (t + t)) * t * t; while (transIdx >= 0) { float *from = &poseData[src0]; From e3e01f95bdfea30ed3366a3b267a1ffad0dd9003 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 17:59:31 +0100 Subject: [PATCH 175/372] 78.5%: hoist pose palette table Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 0a74ba3a3..874a32f37 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -32,6 +32,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl bool interp = false; float t = 0.0f; int floorTime = FloatToInt(currTime); + const PosePalette *const *palettes = paletteBank->GetPalettes(); if (currTime > static_cast(times[0])) { unsigned int numKeys = poseAnim->mNumKeys; @@ -67,7 +68,7 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl mPrevKey = static_cast(key); - const PosePalette *palette = paletteBank->GetPalettes()[poseAnim->mPaletteIndex]; + const PosePalette *palette = palettes[poseAnim->mPaletteIndex]; float *poseData = palette->GetPoseData(); unsigned short *dofIndices = palette->GetDofIndices(); unsigned int numQ = static_cast(palette->GetNumQs()); From bedb95a7f58ae10b94b255dfa0847abad98da7b4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:02:56 +0100 Subject: [PATCH 176/372] 78.5%: reshape pose noninterp stride Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index 874a32f37..a5b2af945 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -106,7 +106,8 @@ bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, fl quatIdx--; } } else { - int src = (poseIdx + 1) * (numQ + numT) * 4 - 4; + int stride = (numQ + numT) * 4; + int src = (poseIdx + 1) * stride - 4; int transIdx = static_cast(numT) - 1; while (transIdx >= 0) { From 144ef9f3dd265bffd9f729ad32f87741621468fd Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:06:45 +0100 Subject: [PATCH 177/372] 78.5%: walk pose blender buffers forward Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 424746b51..4c43d0425 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -176,19 +176,15 @@ void FnPoseBlender::operator delete(void *ptr, size_t size) { void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float *pose1, float *result, const BoneMask *boneMask) { if (!boneMask) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { - int poseIdx = boneIdx * 12; - + for (int boneIdx = 0, poseIdx = 0; boneIdx < numBones; ++boneIdx, poseIdx += 12) { FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); } } else { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = 0, poseIdx = 0; boneIdx < numBones; ++boneIdx, poseIdx += 12) { if (boneMask->GetBone(boneIdx)) { - int poseIdx = boneIdx * 12; - FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); From 41823e5dfa3710725d1f3f6e7d7553dbb4401884 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:13:22 +0100 Subject: [PATCH 178/372] 78.5%: free loader hash pointer directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 69e34fef1..6f1690b2a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -424,8 +424,7 @@ void DynamicLoader::Release() { EAGL4Internal::EAGL4Free(h->isOriginal, h->symbols_num * sizeof(uintptr_t)); } - // TODO how to avoid the null check? - delete h; + EAGL4Internal::EAGL4Free(h, sizeof(HashPointer)); handle = nullptr; } mIsResolved = false; From 9ebbb595488d6fe4c5dd4f31abd00a7a4e350875 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:26:13 +0100 Subject: [PATCH 179/372] 78.5%: move qfast masked dispatch first Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index ae7765220..6608a6419 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -433,15 +433,15 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi } bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (boneMask) { + return EvalSQTMask(currTime, sqt, boneMask); + } if (!mpAnim) { return false; } if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { InitBuffers(); } - if (boneMask) { - return EvalSQTMask(currTime, sqt, boneMask); - } if (mBoneMask) { mBoneMask = nullptr; mPrevKey = -1; From 24de6a8179c2665ad8bd17f27282fee65c60a18e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:29:04 +0100 Subject: [PATCH 180/372] 78.5%: invert qfast top-level branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 404 +++++++++--------- 1 file changed, 202 insertions(+), 202 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 6608a6419..75bf7e923 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -433,244 +433,244 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi } bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { - if (boneMask) { - return EvalSQTMask(currTime, sqt, boneMask); - } - if (!mpAnim) { - return false; - } - if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { - InitBuffers(); - } - if (mBoneMask) { - mBoneMask = nullptr; - mPrevKey = -1; - mNextKey = -1; - } - - DeltaQFast *deltaQ = reinterpret_cast(mpAnim); - float *quatBase = sqt + 4; - unsigned char *boneIdxs = deltaQ->mBoneIdxs; + if (!boneMask) { + if (!mpAnim) { + return false; + } + if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { + InitBuffers(); + } + if (mBoneMask) { + mBoneMask = boneMask; + mPrevKey = -1; + mNextKey = -1; + } - if (deltaQ->mNumBones) { - int floorTime = FloatToInt(currTime); - unsigned short *times = deltaQ->mTimes; - int floorKey; - int prevKey = mPrevKey; - unsigned int binLengthPower; - unsigned int floorDeltaIdx; - int floorBinIdx; - unsigned int binLengthMask; - - if (!times) { - if (floorTime < 0) { - floorKey = 0; - } else if (deltaQ->mNumKeys > floorTime) { - floorKey = floorTime; - } else { - floorKey = deltaQ->mNumKeys - 1; - } - } else { - if (floorTime < times[0]) { - floorKey = 0; - } else { - int timeIndex = prevKey - 1; - if (prevKey < 1) { - timeIndex = 0; + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + float *quatBase = sqt + 4; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (deltaQ->mNumBones) { + int floorTime = FloatToInt(currTime); + unsigned short *times = deltaQ->mTimes; + int floorKey; + int prevKey = mPrevKey; + unsigned int binLengthPower; + unsigned int floorDeltaIdx; + int floorBinIdx; + unsigned int binLengthMask; + + if (!times) { + if (floorTime < 0) { + floorKey = 0; + } else if (deltaQ->mNumKeys > floorTime) { + floorKey = floorTime; + } else { + floorKey = deltaQ->mNumKeys - 1; } + } else { + if (floorTime < times[0]) { + floorKey = 0; + } else { + int timeIndex = prevKey - 1; + if (prevKey < 1) { + timeIndex = 0; + } - if (floorTime >= times[timeIndex]) { - int lastTimeIndex = deltaQ->mNumKeys - 2; - if (timeIndex < lastTimeIndex) { - unsigned int nextTime = times[timeIndex + 1]; - int nextIndex = timeIndex; + if (floorTime >= times[timeIndex]) { + int lastTimeIndex = deltaQ->mNumKeys - 2; + if (timeIndex < lastTimeIndex) { + unsigned int nextTime = times[timeIndex + 1]; + int nextIndex = timeIndex; - while (((timeIndex = nextIndex), static_cast(nextTime) <= floorTime && - ((timeIndex = nextIndex + 1), timeIndex < lastTimeIndex))) { - nextTime = times[nextIndex + 2]; - nextIndex = timeIndex; - } - } - } else { - if (timeIndex > 0) { - do { - timeIndex--; - if (timeIndex < 1) { - break; + while (((timeIndex = nextIndex), static_cast(nextTime) <= floorTime && + ((timeIndex = nextIndex + 1), timeIndex < lastTimeIndex))) { + nextTime = times[nextIndex + 2]; + nextIndex = timeIndex; } - } while (floorTime < times[timeIndex]); + } + } else { + if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { + break; + } + } while (floorTime < times[timeIndex]); + } } - } - floorKey = timeIndex + 1; + floorKey = timeIndex + 1; + } } - } - - binLengthPower = deltaQ->mBinLengthPower; - floorBinIdx = static_cast(floorKey) >> binLengthPower; - binLengthMask = 0x7FFFFFFFU >> (0x1F - binLengthPower); - floorDeltaIdx = floorKey & binLengthMask; - - if (mNextKey == floorKey) { - UMath::Vector4 *swapQs = mNextQs; - mNextQs = mPrevQs; - mPrevQs = swapQs; - mNextKey = prevKey; - } else { - int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; - int prevDeltaIdx = mPrevKey; + binLengthPower = deltaQ->mBinLengthPower; + floorBinIdx = static_cast(floorKey) >> binLengthPower; + binLengthMask = 0x7FFFFFFFU >> (0x1F - binLengthPower); + floorDeltaIdx = floorKey & binLengthMask; - if (prevKey == static_cast(floorKey + 1)) { - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - mPrevQs[ibone] = mNextQs[ibone]; - } + if (mNextKey == floorKey) { + UMath::Vector4 *swapQs = mNextQs; + mNextQs = mPrevQs; + mPrevQs = swapQs; mNextKey = prevKey; - } - - if (prevDeltaIdx == -1 || floorBinIdx != (prevKey >> binLengthPower) || floorDeltaIdx == 0 || - (floorKey < prevKey && !IsReverseDeltaSumEnabled())) { - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - unsigned short *physical = reinterpret_cast(binData + ibone * 6); - - prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[3] = - static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; - } - - prevDeltaIdx = 0; } else { - prevDeltaIdx &= static_cast(binLengthMask); - } + int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; + int prevDeltaIdx = mPrevKey; + + if (prevKey == static_cast(floorKey + 1)) { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + mPrevQs[ibone] = mNextQs[ibone]; + } - if (prevDeltaIdx < static_cast(floorDeltaIdx)) { - unsigned char *deltaData = - reinterpret_cast(binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3); + mNextKey = prevKey; + } - do { + if (prevDeltaIdx == -1 || floorBinIdx != (prevKey >> binLengthPower) || floorDeltaIdx == 0 || + (floorKey < prevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - prevQ[0] = prevQ[0] + minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]; - prevQ[1] = prevQ[1] + minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]; - prevQ[2] = prevQ[2] + minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]; - prevQ[3] = prevQ[3] + - minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * - kQFastDeltaScale6 + - minRange[3]; - deltaData += 3; + unsigned short *physical = reinterpret_cast(binData + ibone * 6); + + prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = + static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } - prevDeltaIdx++; - } while (prevDeltaIdx < static_cast(floorDeltaIdx)); - } else if (static_cast(floorDeltaIdx) < prevDeltaIdx) { - unsigned char *deltaData = reinterpret_cast( - binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3 - 3); - - while ((prevDeltaIdx--), static_cast(floorDeltaIdx) <= prevDeltaIdx) { - for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - prevQ[0] = prevQ[0] - (minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]); - prevQ[1] = prevQ[1] - (minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]); - prevQ[2] = prevQ[2] - (minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]); - prevQ[3] = prevQ[3] - - (minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * - kQFastDeltaScale6 + - minRange[3]); - deltaData -= 3; + + prevDeltaIdx = 0; + } else { + prevDeltaIdx &= static_cast(binLengthMask); + } + + if (prevDeltaIdx < static_cast(floorDeltaIdx)) { + unsigned char *deltaData = + reinterpret_cast(binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3); + + do { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + prevQ[0] = prevQ[0] + minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]; + prevQ[1] = prevQ[1] + minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]; + prevQ[2] = prevQ[2] + minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]; + prevQ[3] = prevQ[3] + + minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * + kQFastDeltaScale6 + + minRange[3]; + deltaData += 3; + } + prevDeltaIdx++; + } while (prevDeltaIdx < static_cast(floorDeltaIdx)); + } else if (static_cast(floorDeltaIdx) < prevDeltaIdx) { + unsigned char *deltaData = reinterpret_cast( + binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3 - 3); + + while ((prevDeltaIdx--), static_cast(floorDeltaIdx) <= prevDeltaIdx) { + for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *minRange = reinterpret_cast(&mMinRangesf[ibone]); + unsigned char b0 = deltaData[0]; + unsigned char b1 = deltaData[1]; + unsigned char b2 = deltaData[2]; + + prevQ[0] = prevQ[0] - (minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]); + prevQ[1] = prevQ[1] - (minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]); + prevQ[2] = prevQ[2] - (minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]); + prevQ[3] = prevQ[3] - + (minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * + kQFastDeltaScale6 + + minRange[3]); + deltaData -= 3; + } } } } - } - mPrevKey = floorKey; - times = deltaQ->mTimes; - bool slerpReqd = false; - float t = 0.0f; + mPrevKey = floorKey; + times = deltaQ->mTimes; + bool slerpReqd = false; + float t = 0.0f; - if (!times) { - float floorTimef = static_cast(floorTime); + if (!times) { + float floorTimef = static_cast(floorTime); - slerpReqd = currTime != floorTimef; - if (slerpReqd) { - t = currTime - floorTimef; - } - } else if (floorKey == 0) { - slerpReqd = currTime != 0.0f; - if (slerpReqd) { - t = currTime / static_cast(times[0]); - } - } else { - float floorTimef = static_cast(times[floorKey - 1]); + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = currTime - floorTimef; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + t = currTime / static_cast(times[0]); + } + } else { + float floorTimef = static_cast(times[floorKey - 1]); - slerpReqd = currTime != floorTimef; - if (slerpReqd) { - t = (currTime - floorTimef) / (static_cast(times[floorKey]) - floorTimef); + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = (currTime - floorTimef) / (static_cast(times[floorKey]) - floorTimef); + } } - } - if (slerpReqd && static_cast(floorKey) < deltaQ->mNumKeys - 1) { - UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); + if (slerpReqd && static_cast(floorKey) < deltaQ->mNumKeys - 1) { + UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); + + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + unsigned char boneIdx = boneIdxs[ibone]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; + } + + goto finish_const_bones; + } for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; - unsigned char boneIdx = boneIdxs[ibone]; - float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; - float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; - float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; - float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); - float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); - - out[0] = x * invNorm; - out[1] = y * invNorm; - out[2] = z * invNorm; - out[3] = w * invNorm; - } + float *out = reinterpret_cast(boneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); - goto finish_const_bones; + out[0] = prevQ[0]; + out[1] = prevQ[1]; + out[2] = prevQ[2]; + out[3] = prevQ[3]; + } } - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *out = reinterpret_cast(boneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); - - out[0] = prevQ[0]; - out[1] = prevQ[1]; - out[2] = prevQ[2]; - out[3] = prevQ[3]; + finish_const_bones: + for (int ibone = 0; ibone < static_cast(deltaQ->mNumConstBones); ibone++) { + unsigned short *physical = reinterpret_cast(reinterpret_cast(mConstPhysical) + ibone * 6); + float *out = reinterpret_cast(mConstBoneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); + + out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } - } - -finish_const_bones: - for (int ibone = 0; ibone < static_cast(deltaQ->mNumConstBones); ibone++) { - unsigned short *physical = reinterpret_cast(reinterpret_cast(mConstPhysical) + ibone * 6); - float *out = reinterpret_cast(mConstBoneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); - out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; + return true; } - - return true; + return EvalSQTMask(currTime, sqt, boneMask); } bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneMask) { From b3f0d3516a9611aaed043ffc6c0d83f26b7d3e4f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:30:35 +0100 Subject: [PATCH 181/372] 78.5%: reload qfast prev key directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 75bf7e923..e413d561b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -451,14 +451,13 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (deltaQ->mNumBones) { - int floorTime = FloatToInt(currTime); - unsigned short *times = deltaQ->mTimes; - int floorKey; - int prevKey = mPrevKey; - unsigned int binLengthPower; - unsigned int floorDeltaIdx; - int floorBinIdx; - unsigned int binLengthMask; + int floorTime = FloatToInt(currTime); + unsigned short *times = deltaQ->mTimes; + int floorKey; + unsigned int binLengthPower; + unsigned int floorDeltaIdx; + int floorBinIdx; + unsigned int binLengthMask; if (!times) { if (floorTime < 0) { @@ -469,13 +468,13 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) floorKey = deltaQ->mNumKeys - 1; } } else { - if (floorTime < times[0]) { - floorKey = 0; - } else { - int timeIndex = prevKey - 1; - if (prevKey < 1) { - timeIndex = 0; - } + if (floorTime < times[0]) { + floorKey = 0; + } else { + int timeIndex = mPrevKey - 1; + if (mPrevKey < 1) { + timeIndex = 0; + } if (floorTime >= times[timeIndex]) { int lastTimeIndex = deltaQ->mNumKeys - 2; @@ -509,26 +508,26 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) binLengthMask = 0x7FFFFFFFU >> (0x1F - binLengthPower); floorDeltaIdx = floorKey & binLengthMask; - if (mNextKey == floorKey) { - UMath::Vector4 *swapQs = mNextQs; - - mNextQs = mPrevQs; - mPrevQs = swapQs; - mNextKey = prevKey; - } else { - int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; - int prevDeltaIdx = mPrevKey; + if (mNextKey == floorKey) { + UMath::Vector4 *swapQs = mNextQs; - if (prevKey == static_cast(floorKey + 1)) { - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - mPrevQs[ibone] = mNextQs[ibone]; - } + mNextQs = mPrevQs; + mPrevQs = swapQs; + mNextKey = mPrevKey; + } else { + int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; + int prevDeltaIdx = mPrevKey; - mNextKey = prevKey; + if (mPrevKey == static_cast(floorKey + 1)) { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + mPrevQs[ibone] = mNextQs[ibone]; } - if (prevDeltaIdx == -1 || floorBinIdx != (prevKey >> binLengthPower) || floorDeltaIdx == 0 || - (floorKey < prevKey && !IsReverseDeltaSumEnabled())) { + mNextKey = mPrevKey; + } + + if (prevDeltaIdx == -1 || floorBinIdx != (mPrevKey >> binLengthPower) || floorDeltaIdx == 0 || + (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); unsigned short *physical = reinterpret_cast(binData + ibone * 6); From e06d81807546499cdde2b4e7b459b905bbb48d4e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 18:31:11 +0100 Subject: [PATCH 182/372] 78.5%: inline qfast bin length power Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index e413d561b..950e25b26 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -454,7 +454,6 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) int floorTime = FloatToInt(currTime); unsigned short *times = deltaQ->mTimes; int floorKey; - unsigned int binLengthPower; unsigned int floorDeltaIdx; int floorBinIdx; unsigned int binLengthMask; @@ -503,10 +502,9 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } } - binLengthPower = deltaQ->mBinLengthPower; - floorBinIdx = static_cast(floorKey) >> binLengthPower; - binLengthMask = 0x7FFFFFFFU >> (0x1F - binLengthPower); - floorDeltaIdx = floorKey & binLengthMask; + floorBinIdx = static_cast(floorKey) >> deltaQ->mBinLengthPower; + binLengthMask = 0x7FFFFFFFU >> (0x1F - deltaQ->mBinLengthPower); + floorDeltaIdx = floorKey & binLengthMask; if (mNextKey == floorKey) { UMath::Vector4 *swapQs = mNextQs; @@ -526,7 +524,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mNextKey = mPrevKey; } - if (prevDeltaIdx == -1 || floorBinIdx != (mPrevKey >> binLengthPower) || floorDeltaIdx == 0 || + if (prevDeltaIdx == -1 || floorBinIdx != (mPrevKey >> deltaQ->mBinLengthPower) || floorDeltaIdx == 0 || (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); From acf9c9a18f577442399c97a14777d46fc2e8ebf8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:04:12 +0100 Subject: [PATCH 183/372] 78.6%: use packed qfast min-range base Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 950e25b26..d60b4b572 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -192,9 +192,10 @@ FnDeltaQFast::~FnDeltaQFast() { void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { DeltaQFast *deltaQ = reinterpret_cast(anim); unsigned char numBones = deltaQ->mNumBones; + DeltaQFastMinRange *minRange = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); mpAnim = anim; - mBins = reinterpret_cast(deltaQ) + 0x14 + (numBones << 4); + mBins = reinterpret_cast(minRange) + numBones * sizeof(DeltaQFastMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); @@ -205,7 +206,6 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { if (numBones != 0) { unsigned char *block = reinterpret_cast(MemoryPoolManager::NewBlock(numBones << 6)); unsigned char *qBlock = block + (numBones << 5); - DeltaQFastMinRange *minRange = reinterpret_cast(deltaQ + 1); mMinRangesf = reinterpret_cast(block); mPrevQBlock = qBlock; From b01dfdf0a41cacc6d337a8b793b9f6cb55942f71 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:06:27 +0100 Subject: [PATCH 184/372] 78.6%: tighten qfast const-physical layout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index d60b4b572..b4d868051 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -36,13 +36,13 @@ unsigned char *DeltaQFast::GetConstBoneIdx() { unsigned int numBones = mNumBones; unsigned int binLength = 1u << mBinLengthPower; unsigned int numBins = mNumKeys / binLength; - unsigned int remainder = mNumKeys - numBins * binLength; + int remainder = mNumKeys - numBins * binLength; int s = reinterpret_cast(this) + 0x12; s += numBones * sizeof(DeltaQFastMinRange); s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; - if (remainder != 0) { + if (remainder > 0) { s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); } @@ -53,13 +53,13 @@ DeltaQFastPhysical *DeltaQFast::GetConstPhysical() { unsigned int numBones = mNumBones; unsigned int binLength = 1u << mBinLengthPower; unsigned int numBins = mNumKeys / binLength; - unsigned int remainder = mNumKeys - numBins * binLength; + int remainder = mNumKeys - numBins * binLength; int s = reinterpret_cast(this) + 0x12; s += numBones * sizeof(DeltaQFastMinRange); s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; - if (remainder != 0) { + if (remainder > 0) { s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); } From 618dba4a2ae8ca781022d0e7847d598728223d45 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:09:36 +0100 Subject: [PATCH 185/372] 78.6%: store qfast anim before layout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index b4d868051..dd23eb629 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -191,10 +191,10 @@ FnDeltaQFast::~FnDeltaQFast() { void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { DeltaQFast *deltaQ = reinterpret_cast(anim); - unsigned char numBones = deltaQ->mNumBones; - DeltaQFastMinRange *minRange = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); mpAnim = anim; + unsigned char numBones = deltaQ->mNumBones; + DeltaQFastMinRange *minRange = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); mBins = reinterpret_cast(minRange) + numBones * sizeof(DeltaQFastMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); From 222e800dd902858823b84234e8b070dc6bcabb40 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:19:47 +0100 Subject: [PATCH 186/372] 78.6%: remove qfast lazy init checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index dd23eb629..3b7dc467d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -437,9 +437,6 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) if (!mpAnim) { return false; } - if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { - InitBuffers(); - } if (mBoneMask) { mBoneMask = boneMask; mPrevKey = -1; @@ -674,9 +671,6 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM if (!mpAnim) { return false; } - if (!mMinRangesf && reinterpret_cast(mpAnim)->mNumBones) { - InitBuffers(); - } if (boneMask != mBoneMask) { mBoneMask = boneMask; mPrevKey = -1; From 6153e623365c7e538159e3925d9917c5e837e9c8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:20:51 +0100 Subject: [PATCH 187/372] 78.7%: remove qfast eval null guards Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 3b7dc467d..467ecb412 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -434,9 +434,6 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (!boneMask) { - if (!mpAnim) { - return false; - } if (mBoneMask) { mBoneMask = boneMask; mPrevKey = -1; @@ -668,9 +665,6 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneMask) { - if (!mpAnim) { - return false; - } if (boneMask != mBoneMask) { mBoneMask = boneMask; mPrevKey = -1; From e0afa6bbcb4e18581007eea99a185cfa519d4639 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:30:28 +0100 Subject: [PATCH 188/372] 78.7%: simplify ExtractQuatTrans access Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 4c43d0425..d48432ca1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -44,12 +44,6 @@ void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTra float xx = mat[0]; float yy = mat[5]; float zz = mat[10]; - float xy = mat[1]; - float xz = mat[2]; - float yx = mat[4]; - float yz = mat[6]; - float zx = mat[8]; - float zy = mat[9]; float trace = xx + yy + zz; if (trace > 0.0f) { @@ -57,46 +51,54 @@ void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTra float invS = 0.5f / s; retQuat->w = s * 0.5f; - retQuat->z = (xy - yx) * invS; - retQuat->x = (yz - zy) * invS; - retQuat->y = (zx - xz) * invS; + retQuat->z = (mat[1] - mat[4]) * invS; + retQuat->x = (mat[6] - mat[9]) * invS; + retQuat->y = (mat[8] - mat[2]) * invS; } else { - float maxDiag = yy; + if (xx < yy) { + if (zz > yy) { + float s = EAGL4Anim::FastSqrt((zz - (xx + yy)) + 1.0f); - if (xx >= yy) { - maxDiag = xx; - } - - if (zz > maxDiag) { - float s = EAGL4Anim::FastSqrt((zz - (xx + yy)) + 1.0f); + retQuat->z = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (mat[1] - mat[4]) * s; + retQuat->y = (mat[9] + mat[6]) * s; + retQuat->x = (mat[8] + mat[2]) * s; + } else { + float s = EAGL4Anim::FastSqrt((yy - (zz + xx)) + 1.0f); - retQuat->z = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - retQuat->w = (xy - yx) * s; - retQuat->y = (zy + yz) * s; - retQuat->x = (zx + xz) * s; - } else if (xx >= yy) { - float s = EAGL4Anim::FastSqrt((xx - (yy + zz)) + 1.0f); - - retQuat->x = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; + retQuat->y = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (mat[8] - mat[2]) * s; + retQuat->x = (mat[4] + mat[1]) * s; + retQuat->z = (mat[6] + mat[9]) * s; } - retQuat->w = (yz - zy) * s; - retQuat->y = (xy + yx) * s; - retQuat->z = (xz + zx) * s; } else { - float s = EAGL4Anim::FastSqrt((yy - (zz + xx)) + 1.0f); + if (zz > xx) { + float s = EAGL4Anim::FastSqrt((zz - (xx + yy)) + 1.0f); - retQuat->y = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; + retQuat->z = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (mat[1] - mat[4]) * s; + retQuat->y = (mat[9] + mat[6]) * s; + retQuat->x = (mat[8] + mat[2]) * s; + } else { + float s = EAGL4Anim::FastSqrt((xx - (yy + zz)) + 1.0f); + + retQuat->x = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + retQuat->w = (mat[6] - mat[9]) * s; + retQuat->y = (mat[1] + mat[4]) * s; + retQuat->z = (mat[2] + mat[8]) * s; } - retQuat->w = (zx - xz) * s; - retQuat->x = (yx + xy) * s; - retQuat->z = (yz + zy) * s; } } From d2123e74a1e7f595341e6322a3d9d4dd321f2db2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:37:39 +0100 Subject: [PATCH 189/372] 78.7%: decode qfast next physical bitfields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 467ecb412..e422c1b08 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -291,19 +291,19 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; if (ceilBinIdx != floorBinIdx) { - unsigned int numBones = deltaQ->mNumBones; - unsigned short *physical = reinterpret_cast(binData); + unsigned char numBones = deltaQ->mNumBones; + DeltaQFastPhysical *physical = reinterpret_cast(binData); - for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + for (int ibone = 0; ibone < numBones; ibone++) { float *nextQ = reinterpret_cast(&mNextQs[ibone]); - nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[3] = static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * + nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - physical += 3; + physical++; } } else { unsigned int numBones = deltaQ->mNumBones; From 0a30e61c18752aa4eb2b5ca2b237578bfccafe41 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:38:34 +0100 Subject: [PATCH 190/372] 78.7%: decode masked qfast physical bitfields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index e422c1b08..6f20809fc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -386,23 +386,23 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (ceilBinIdx != floorBinIdx) { - unsigned int numBones = deltaQ->mNumBones; - unsigned short *physical = reinterpret_cast(binData); + unsigned char numBones = deltaQ->mNumBones; + DeltaQFastPhysical *physical = reinterpret_cast(binData); - for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + for (int ibone = 0; ibone < numBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { float *nextQ = reinterpret_cast(&mNextQs[ibone]); - nextQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[3] = - static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } - physical += 3; + physical++; } } else { unsigned int numBones = deltaQ->mNumBones; From 5fcecb4b12e717bf8516fdfc4043637e6f158c1d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:39:57 +0100 Subject: [PATCH 191/372] 78.7%: decode qfast masked floor physical via bitfields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 6f20809fc..7f17d0657 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -689,13 +689,13 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM continue; } - unsigned short *physical = reinterpret_cast(&floorPhys[ibone]); + DeltaQFastPhysical *physical = &floorPhys[ibone]; float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[3] = static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * + prevQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } From 2ef513605a2672f5b9288ad87902191358f598dd Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:40:32 +0100 Subject: [PATCH 192/372] 78.8%: decode qfast masked const physical via bitfields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 7f17d0657..f53d49616 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -787,13 +787,13 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM continue; } - unsigned short *physical = reinterpret_cast(&mConstPhysical[ibone]); + DeltaQFastPhysical *physical = &mConstPhysical[ibone]; float *out = &quatBase[boneIdx * 12]; - out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[3] = static_cast(static_cast(((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4) | (physical[2] & 0xF))) * + out[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } From 0be2fb02e7c6e280b1b3531403534d97b2676277 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:41:04 +0100 Subject: [PATCH 193/372] 78.8%: decode qfast floor physical via bitfields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index f53d49616..23eb83ade 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -518,17 +518,17 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mNextKey = mPrevKey; } - if (prevDeltaIdx == -1 || floorBinIdx != (mPrevKey >> deltaQ->mBinLengthPower) || floorDeltaIdx == 0 || + if (prevDeltaIdx == -1 || floorBinIdx != (mPrevKey >> deltaQ->mBinLengthPower) || floorDeltaIdx == 0 || (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - unsigned short *physical = reinterpret_cast(binData + ibone * 6); + DeltaQFastPhysical *physical = reinterpret_cast(binData + ibone * 6); - prevQ[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; prevQ[3] = - static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } From 5673515b483c700ca692fdd8d340e4e9bb4c8466 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:41:29 +0100 Subject: [PATCH 194/372] 78.8%: decode qfast const physical via bitfields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 23eb83ade..3f7cc488d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -648,13 +648,13 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) finish_const_bones: for (int ibone = 0; ibone < static_cast(deltaQ->mNumConstBones); ibone++) { - unsigned short *physical = reinterpret_cast(reinterpret_cast(mConstPhysical) + ibone * 6); + DeltaQFastPhysical *physical = reinterpret_cast(reinterpret_cast(mConstPhysical) + ibone * 6); float *out = reinterpret_cast(mConstBoneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); - out[0] = static_cast(physical[0] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[1] = static_cast(physical[1] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[2] = static_cast(physical[2] >> 4) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - out[3] = static_cast(static_cast((physical[2] & 0xF) | ((physical[0] & 0xF) << 8) | ((physical[1] & 0xF) << 4))) * + out[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; } From 4df6d6851340541e1219ce68874d799073831348 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:47:08 +0100 Subject: [PATCH 195/372] 78.8%: tighten deltaq RecoverW sign test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index b25e213db..2100d2b53 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -108,7 +108,7 @@ static inline void RecoverW(int signBit, UMath::Vector4 &q) { if (ndotn <= kFloatOne) { q.w = FastSqrt(kFloatOne - ndotn); - if (signBit) { + if (signBit > 0) { q.w = -q.w; } } else { From dc065cacf9dd68b5cc14f7ab6a35a743b561da3f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 19:59:34 +0100 Subject: [PATCH 196/372] 78.8%: reload deltaq init numbones Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 2100d2b53..b4ecb475c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -186,8 +186,9 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); - if (numBones != 0) { - UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + if (deltaQ->mNumBones != 0) { + UMath::Vector4 *prevQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); mMinRanges = minRanges; mPrevQBlock = prevQs; @@ -402,8 +403,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); - if (numBones != 0) { - UMath::Vector4 *prevQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs))); + if (deltaQ->mNumBones != 0) { + UMath::Vector4 *prevQs = + reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); mMinRanges = minRanges; mPrevQBlock = prevQs; From c0aca1a09df6383e21b28b6ba19896313b44c08c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:02:07 +0100 Subject: [PATCH 197/372] 78.8%: inline singleq init layout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 320f27296..b997ac83b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -199,9 +199,12 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); if (!mPrevQs) { - DeltaSingleQMinRange *minRanges = GetSingleQMinRanges(deltaQ); - mBins = GetSingleQBinStart(deltaQ); - mBinSize = GetSingleQBinSize(deltaQ); + unsigned char numBones = deltaQ->mNumBones; + DeltaSingleQMinRange *minRanges = + reinterpret_cast(reinterpret_cast(deltaQ) + 0x10); + + mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaSingleQMinRange); + mBinSize = AlignSize2(numBones * ((1 << deltaQ->mBinLengthPower) + 1)); if (deltaQ->mNumBones != 0) { float eul[3]; @@ -405,9 +408,12 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); if (!mPrevQs) { - DeltaSingleQMinRange *minRanges = GetSingleQMinRanges(deltaQ); - mBins = GetSingleQBinStart(deltaQ); - mBinSize = GetSingleQBinSize(deltaQ); + unsigned char numBones = deltaQ->mNumBones; + DeltaSingleQMinRange *minRanges = + reinterpret_cast(reinterpret_cast(deltaQ) + 0x10); + + mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaSingleQMinRange); + mBinSize = AlignSize2(numBones * ((1 << deltaQ->mBinLengthPower) + 1)); if (deltaQ->mNumBones != 0) { float eul[3]; From 5ade5a332d2d24b0846534f99a33ac097ac99c85 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:04:40 +0100 Subject: [PATCH 198/372] 78.8%: shape qfast buffer offsets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 3f7cc488d..50ba347af 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -204,13 +204,17 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { mPrevKey = -1; if (numBones != 0) { - unsigned char *block = reinterpret_cast(MemoryPoolManager::NewBlock(numBones << 6)); - unsigned char *qBlock = block + (numBones << 5); + unsigned int qBlockOffset = numBones << 5; + unsigned int nextQBlockOffset = numBones << 4; + unsigned char *block = + reinterpret_cast(MemoryPoolManager::NewBlock(qBlockOffset + nextQBlockOffset + nextQBlockOffset)); + unsigned char *qBlock = block + qBlockOffset; + unsigned char *nextQBlock = qBlock + nextQBlockOffset; mMinRangesf = reinterpret_cast(block); mPrevQBlock = qBlock; mPrevQs = reinterpret_cast(qBlock); - mNextQBlock = qBlock + (numBones << 4); + mNextQBlock = nextQBlock; mNextQs = reinterpret_cast(mNextQBlock); for (int ibone = 0; ibone < numBones; ibone++) { From ede5f1656ea42ab0f6014d6b37e1b735c998ca1d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:06:30 +0100 Subject: [PATCH 199/372] 78.8%: delay qfast reset stores Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 50ba347af..861c97c03 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -199,9 +199,6 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); - mBoneMask = nullptr; - mNextKey = -1; - mPrevKey = -1; if (numBones != 0) { unsigned int qBlockOffset = numBones << 5; @@ -231,6 +228,10 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { minRangefValues[7] = minRangeValues[7] * kQFastMinScale16; } } + + mBoneMask = nullptr; + mNextKey = -1; + mPrevKey = -1; } bool FnDeltaQFast::GetLength(float &timeLength) const { From 7515e28a0eeefb3e7ea128a7ae531297bf9c3bae Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:07:19 +0100 Subject: [PATCH 200/372] 78.8%: reload qfast setup bone count Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 861c97c03..50dba316f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -200,7 +200,7 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); mBinSize = AlignSize2(numBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); - if (numBones != 0) { + if (deltaQ->mNumBones != 0) { unsigned int qBlockOffset = numBones << 5; unsigned int nextQBlockOffset = numBones << 4; unsigned char *block = From f3312db2e0d361b18f39c94e9fa086fcc2f7d6fb Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:09:06 +0100 Subject: [PATCH 201/372] 78.9%: reload qfast bin size bones Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 50dba316f..d38813fdf 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -198,7 +198,7 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { mBins = reinterpret_cast(minRange) + numBones * sizeof(DeltaQFastMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = AlignSize2(numBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); + mBinSize = AlignSize2(deltaQ->mNumBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); if (deltaQ->mNumBones != 0) { unsigned int qBlockOffset = numBones << 5; From 5679f916938ed671bd530eb1ca5d1817753b6051 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:11:21 +0100 Subject: [PATCH 202/372] 78.9%: reload deltaq bin size bones Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index b4ecb475c..4bb0aa6f2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -184,7 +184,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); + mBinSize = AlignSize2(deltaQ->mNumBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); if (deltaQ->mNumBones != 0) { UMath::Vector4 *prevQs = @@ -401,7 +401,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = AlignSize2(numBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); + mBinSize = AlignSize2(deltaQ->mNumBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); if (deltaQ->mNumBones != 0) { UMath::Vector4 *prevQs = From 49558adfd6dec23c8bb65249c3dd1529184df9f2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:12:54 +0100 Subject: [PATCH 203/372] 78.9%: reload masked singleq bin size Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index b997ac83b..77f06489f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -413,7 +413,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo reinterpret_cast(reinterpret_cast(deltaQ) + 0x10); mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaSingleQMinRange); - mBinSize = AlignSize2(numBones * ((1 << deltaQ->mBinLengthPower) + 1)); + mBinSize = AlignSize2(deltaQ->mNumBones * ((1 << deltaQ->mBinLengthPower) + 1)); if (deltaQ->mNumBones != 0) { float eul[3]; From 0b3b9b0bf94bdd339672cce2c26c1bab658ac39f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:13:59 +0100 Subject: [PATCH 204/372] 78.9%: reorder qfast block size sum Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index d38813fdf..b29978f3f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -204,7 +204,7 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { unsigned int qBlockOffset = numBones << 5; unsigned int nextQBlockOffset = numBones << 4; unsigned char *block = - reinterpret_cast(MemoryPoolManager::NewBlock(qBlockOffset + nextQBlockOffset + nextQBlockOffset)); + reinterpret_cast(MemoryPoolManager::NewBlock(nextQBlockOffset + nextQBlockOffset + qBlockOffset)); unsigned char *qBlock = block + qBlockOffset; unsigned char *nextQBlock = qBlock + nextQBlockOffset; From 35b1c0cb5e2e5f10e5d9993e9159f3ed910facca Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:23:59 +0100 Subject: [PATCH 205/372] 78.9%: reload qfast init loop bones Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index b29978f3f..b25b0e2b8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -214,7 +214,7 @@ void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { mNextQBlock = nextQBlock; mNextQs = reinterpret_cast(mNextQBlock); - for (int ibone = 0; ibone < numBones; ibone++) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { unsigned short *minRangeValues = reinterpret_cast(&minRange[ibone]); float *minRangefValues = reinterpret_cast(&mMinRangesf[ibone]); From 94b7006a36756de85ef297ae69298e070b2694ea Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:29:27 +0100 Subject: [PATCH 206/372] 78.9%: reload masked qfast loop bones Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index b25b0e2b8..a9cf0013b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -394,7 +394,7 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi unsigned char numBones = deltaQ->mNumBones; DeltaQFastPhysical *physical = reinterpret_cast(binData); - for (int ibone = 0; ibone < numBones; ibone++) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { float *nextQ = reinterpret_cast(&mNextQs[ibone]); @@ -413,7 +413,7 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi unsigned int numBones = deltaQ->mNumBones; unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; - for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { float *prevQ = reinterpret_cast(&mPrevQs[ibone]); From 5ff645d033475c87d8ba88149550835165e9582c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 20:52:44 +0100 Subject: [PATCH 207/372] 79.1%: rewrite Transform::ExtractQuatTrans Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 125 +++++++++--------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index d48432ca1..277e0c4f6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -5,6 +5,58 @@ namespace EAGL4 { +static inline void EAGL4m3toquat(const UMath::Matrix3 *mat, UMath::Vector4 *result) { + float s; + float trace = (*mat)[0][0] + (*mat)[1][1] + (*mat)[2][2]; + + if (trace > 0.0f) { + s = EAGL4Anim::FastSqrt(trace + 1.0f); + result->w = s * 0.5f; + s = 0.5f / s; + result->x = ((*mat)[1][2] - (*mat)[2][1]) * s; + result->y = ((*mat)[2][0] - (*mat)[0][2]) * s; + result->z = ((*mat)[0][1] - (*mat)[1][0]) * s; + } else { + unsigned long i = 0; + + if ((*mat)[1][1] > (*mat)[0][0]) { + i = 1; + } + if ((*mat)[2][2] > (*mat)[i][i]) { + i = 2; + } + + if (i == 0) { + s = EAGL4Anim::FastSqrt(((*mat)[0][0] - ((*mat)[1][1] + (*mat)[2][2])) + 1.0f); + result->x = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[1][2] - (*mat)[2][1]) * s; + result->y = ((*mat)[0][1] + (*mat)[1][0]) * s; + result->z = ((*mat)[0][2] + (*mat)[2][0]) * s; + } else if (i == 1) { + s = EAGL4Anim::FastSqrt(((*mat)[1][1] - ((*mat)[2][2] + (*mat)[0][0])) + 1.0f); + result->y = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[2][0] - (*mat)[0][2]) * s; + result->x = ((*mat)[1][0] + (*mat)[0][1]) * s; + result->z = ((*mat)[1][2] + (*mat)[2][1]) * s; + } else { + s = EAGL4Anim::FastSqrt(((*mat)[2][2] - ((*mat)[0][0] + (*mat)[1][1])) + 1.0f); + result->z = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[0][1] - (*mat)[1][0]) * s; + result->x = ((*mat)[2][0] + (*mat)[0][2]) * s; + result->y = ((*mat)[2][1] + (*mat)[1][2]) * s; + } + } +} + void MultMatrix(const UMath::Matrix4 *pm1, const UMath::Matrix4 *pm2, UMath::Matrix4 *presult) { const float *m1 = pm1->GetElements(); const float *m2 = pm2->GetElements(); @@ -41,66 +93,19 @@ void Transform::PostMult(const Transform &second) { void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTrans) const { const float *mat = m.GetElements(); - float xx = mat[0]; - float yy = mat[5]; - float zz = mat[10]; - float trace = xx + yy + zz; - - if (trace > 0.0f) { - float s = EAGL4Anim::FastSqrt(trace + 1.0f); - float invS = 0.5f / s; - - retQuat->w = s * 0.5f; - retQuat->z = (mat[1] - mat[4]) * invS; - retQuat->x = (mat[6] - mat[9]) * invS; - retQuat->y = (mat[8] - mat[2]) * invS; - } else { - if (xx < yy) { - if (zz > yy) { - float s = EAGL4Anim::FastSqrt((zz - (xx + yy)) + 1.0f); - - retQuat->z = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - retQuat->w = (mat[1] - mat[4]) * s; - retQuat->y = (mat[9] + mat[6]) * s; - retQuat->x = (mat[8] + mat[2]) * s; - } else { - float s = EAGL4Anim::FastSqrt((yy - (zz + xx)) + 1.0f); - - retQuat->y = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - retQuat->w = (mat[8] - mat[2]) * s; - retQuat->x = (mat[4] + mat[1]) * s; - retQuat->z = (mat[6] + mat[9]) * s; - } - } else { - if (zz > xx) { - float s = EAGL4Anim::FastSqrt((zz - (xx + yy)) + 1.0f); - - retQuat->z = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - retQuat->w = (mat[1] - mat[4]) * s; - retQuat->y = (mat[9] + mat[6]) * s; - retQuat->x = (mat[8] + mat[2]) * s; - } else { - float s = EAGL4Anim::FastSqrt((xx - (yy + zz)) + 1.0f); - - retQuat->x = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - retQuat->w = (mat[6] - mat[9]) * s; - retQuat->y = (mat[1] + mat[4]) * s; - retQuat->z = (mat[2] + mat[8]) * s; - } - } - } + UMath::Matrix3 m3; + + m3[0][0] = mat[0]; + m3[0][1] = mat[1]; + m3[0][2] = mat[2]; + m3[1][0] = mat[4]; + m3[1][1] = mat[5]; + m3[1][2] = mat[6]; + m3[2][0] = mat[8]; + m3[2][1] = mat[9]; + m3[2][2] = mat[10]; + + EAGL4m3toquat(&m3, retQuat); retTrans->x = mat[12]; retTrans->y = mat[13]; From c7190cc0562e60cc16aa04cb0bf87c555ea92c5c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:07:30 +0100 Subject: [PATCH 208/372] 79.3%: reshape DynamicLoader::Initialize Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 257 ++++++++++-------- .../Indep/Src/EAGL4Anim/eagl4supportdlopen.h | 8 +- 2 files changed, 144 insertions(+), 121 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 6f1690b2a..0e2b4da59 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -18,25 +18,21 @@ static const char kDynamicSymbolsMsg[] = "EAGL4::dynamic symbols"; static const char kDynamicSymbols2Msg[] = "EAGL4::dynamic symbols 2"; static const char gRuntimeAllocType[] = "RUNTIME_ALLOC::"; -static inline unsigned short ByteSwap16(unsigned short value) { - return static_cast((value << 8) | (value >> 8)); +static inline unsigned short htotus(unsigned short s) { + return static_cast((s << 8) | (s >> 8)); } -static inline unsigned int ByteSwap32(unsigned int value) { - return ((value >> 16) & 0xFF) << 8 | (value >> 24) | (((value & 0xFF) << 8 | ((value & 0xFFFF) >> 8)) << 16); +static inline unsigned int htotul(unsigned int l) { + unsigned int ul; + + ul = htotus(static_cast(l)); + ul <<= 16; + ul |= htotus(static_cast(l >> 16)); + return ul; } -static inline char *ResolveLoaderAddress(char *data, unsigned int dataLen, char *reloc, unsigned int offset) { - if (offset < dataLen) { - return data + offset; - } - if (reloc) { - return reloc + (offset - dataLen); - } - if (dataLen < offset) { - return nullptr; - } - return data + offset; +static inline unsigned int ByteSwap32(unsigned int value) { + return ((value >> 16) & 0xFF) << 8 | (value >> 24) | (((value & 0xFF) << 8 | ((value & 0xFFFF) >> 8)) << 16); } static inline const char *GetLoaderSymbolType(const char *name) { @@ -49,6 +45,38 @@ static inline const char *GetLoaderSymbolType(const char *name) { } // namespace +inline void *HashPointer::operator new(size_t size) { + return EAGL4Internal::EAGL4Malloc(size, nullptr); +} + +inline HashPointer::HashPointer(DynamicLoader *pDL) { + e = nullptr; + mpDynamicLoader = pDL; + pSearchFunction = nullptr; + next = nullptr; + prev = nullptr; + strtab = nullptr; + resolved = false; + symbols_num = 0; + symtab = nullptr; + sections = nullptr; + chain = nullptr; + isOriginal = nullptr; +} + +inline void *DynamicLoader::ELFAddr(unsigned int offset) { + if (offset < mDataLen) { + return mpData + offset; + } + if (mpReloc) { + return mpReloc + (offset - mDataLen); + } + if (mDataLen < offset) { + return nullptr; + } + return mpData + offset; +} + static HashPointer *hashhead; SymbolPool DynamicLoader::gSymbolPool; @@ -264,133 +292,130 @@ void DynamicLoader::Resolve() { } void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { - HashPointer *h = reinterpret_cast(EAGL4Internal::EAGL4Malloc(sizeof(HashPointer), nullptr)); - - h->pSearchFunction = pSearchFunction; - h->next = nullptr; - h->prev = nullptr; - h->strtab = nullptr; - h->resolved = false; - h->symbols_num = 0; - h->symtab = nullptr; - h->sections = nullptr; - h->chain = nullptr; - h->isOriginal = nullptr; - h->mpDynamicLoader = this; - h->e = reinterpret_cast(mpData); - - ELFHeader *e = h->e; - - e->e_type = ByteSwap16(e->e_type); - e->e_machine = ByteSwap16(e->e_machine); - e->e_version = ByteSwap32(e->e_version); - e->e_entry = ByteSwap32(e->e_entry); - e->e_phoff = ByteSwap32(e->e_phoff); - e->e_shoff = ByteSwap32(e->e_shoff); - e->e_flags = ByteSwap32(e->e_flags); - e->e_ehsize = ByteSwap16(e->e_ehsize); - e->e_phentsize = ByteSwap16(e->e_phentsize); - e->e_phnum = ByteSwap16(e->e_phnum); - e->e_shentsize = ByteSwap16(e->e_shentsize); - e->e_shnum = ByteSwap16(e->e_shnum); - e->e_shstrndx = ByteSwap16(e->e_shstrndx); - - h->sections = reinterpret_cast(ResolveLoaderAddress(mpData, mDataLen, mpReloc, e->e_shoff)); - - for (int i = 0; i < e->e_shnum; i++) { - ELFSectionHeader *section = &h->sections[i]; - - section->sh_name = ByteSwap32(section->sh_name); - section->sh_type = ByteSwap32(section->sh_type); - section->sh_flags = ByteSwap32(section->sh_flags); - section->sh_addr = ByteSwap32(section->sh_addr); - section->sh_offset = ByteSwap32(section->sh_offset); - section->sh_size = ByteSwap32(section->sh_size); - section->sh_link = ByteSwap32(section->sh_link); - section->sh_info = ByteSwap32(section->sh_info); - section->sh_addralign = ByteSwap32(section->sh_addralign); - section->sh_entsize = ByteSwap32(section->sh_entsize); - - section->sh_voffset = ResolveLoaderAddress(mpData, mDataLen, mpReloc, section->sh_offset); + int i; + HashPointer *pHP = new HashPointer(this); + HashPointer &h = *pHP; + ELFSectionHeader *sheader; + ELFHeader *e; + char *shstrtab; + void *p; + + e = reinterpret_cast(mpData); + h.e = e; + + e->e_type = htotus(e->e_type); + e->e_machine = htotus(e->e_machine); + e->e_version = htotul(e->e_version); + e->e_entry = htotul(e->e_entry); + e->e_phoff = htotul(e->e_phoff); + e->e_shoff = htotul(e->e_shoff); + e->e_flags = htotul(e->e_flags); + e->e_ehsize = htotus(e->e_ehsize); + e->e_phentsize = htotus(e->e_phentsize); + e->e_phnum = htotus(e->e_phnum); + e->e_shentsize = htotus(e->e_shentsize); + e->e_shnum = htotus(e->e_shnum); + e->e_shstrndx = htotus(e->e_shstrndx); + + p = ELFAddr(e->e_shoff); + h.sections = reinterpret_cast(p); + + for (i = 0; i < e->e_shnum; i++) { + sheader = &h.sections[i]; + + sheader->sh_name = htotul(sheader->sh_name); + sheader->sh_type = htotul(sheader->sh_type); + sheader->sh_flags = htotul(sheader->sh_flags); + sheader->sh_addr = htotul(sheader->sh_addr); + sheader->sh_offset = htotul(sheader->sh_offset); + sheader->sh_size = htotul(sheader->sh_size); + sheader->sh_link = htotul(sheader->sh_link); + sheader->sh_info = htotul(sheader->sh_info); + sheader->sh_addralign = htotul(sheader->sh_addralign); + sheader->sh_entsize = htotul(sheader->sh_entsize); + + p = ELFAddr(sheader->sh_offset); + sheader->sh_voffset = p; } - char *sectionNameTable = reinterpret_cast(h->sections[e->e_shstrndx].sh_voffset); + shstrtab = reinterpret_cast(h.sections[e->e_shstrndx].sh_voffset); - for (int i = 0; i < e->e_shnum; i++) { - ELFSectionHeader *section = &h->sections[i]; + for (i = 0; i < e->e_shnum; i++) { + sheader = &h.sections[i]; - if (section->sh_type == SHT_STRTAB) { - section->sh_vlink = h->sections[section->sh_link].sh_voffset; - if (strcmp(kStrTabName, sectionNameTable + section->sh_name) == 0) { - char *stringEntry = reinterpret_cast(section->sh_voffset); - int remaining = section->sh_size; + if (sheader->sh_type == SHT_STRTAB) { + sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; + if (strcmp(kStrTabName, shstrtab + sheader->sh_name) == 0) { + char *stringEntry = reinterpret_cast(sheader->sh_voffset); + int len = sheader->sh_size; + const char *TYPE_SEPARATOR = kNamespaceMarker; + char *t; + unsigned int nameLength; - h->strtab = stringEntry; - while (remaining > 0) { - int len = strlen(stringEntry); + h.strtab = stringEntry; + while (len > 0) { if (stringEntry[0] == '_' && stringEntry[1] == '_') { - char *scope = strstr(stringEntry + 2, kNamespaceMarker); - if (scope) { - char temp[128]; - *scope = 0; - strcpy(temp, stringEntry + 2); - int markerLen = strlen(kNamespaceMarker); - int tailLen = strlen(scope + markerLen); - memmove(stringEntry, scope + markerLen, tailLen + 1); - stringEntry[tailLen + 1] = '\x7F'; - strcpy(stringEntry + tailLen + 2, temp); - len = strlen(stringEntry); + t = strstr(stringEntry + 2, TYPE_SEPARATOR); + if (t) { + *t = 0; + nameLength = strlen(t + 3); + memmove(stringEntry + nameLength + 2, stringEntry + 2, strlen(stringEntry + 2) + 1); + memmove(stringEntry, t + 3, nameLength + 1); + stringEntry[nameLength + 1] = '\x7F'; } } - stringEntry += len + 1; - remaining -= len + 1; + nameLength = strlen(stringEntry) + 1; + stringEntry += nameLength; + len -= nameLength; } } - } else if (section->sh_type == SHT_SYMTAB || section->sh_type == SHT_STRTAB) { - section->sh_vlink = h->sections[section->sh_link].sh_voffset; - if (strcmp(kSymTabName, sectionNameTable + section->sh_name) == 0) { - h->symtab = reinterpret_cast(section->sh_voffset); - h->symbols_num = static_cast(section->sh_size >> 4); + } else if (sheader->sh_type == SHT_SYMTAB || sheader->sh_type == SHT_STRTAB) { + sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; + if (strcmp(kSymTabName, shstrtab + sheader->sh_name) == 0) { + h.symtab = reinterpret_cast(sheader->sh_voffset); + h.symbols_num = static_cast(sheader->sh_size >> 4); } - } else if (section->sh_type == SHT_REL) { - section->sh_vinfo = h->sections[section->sh_info].sh_voffset; - section->sh_vlink = h->sections[section->sh_link].sh_voffset; + } else if (sheader->sh_type == SHT_REL) { + sheader->sh_vinfo = h.sections[sheader->sh_info].sh_voffset; + sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; } } - for (int i = 0; i < 0x100; i++) { - h->hash[i] = static_cast(-1); + h.pSearchFunction = pSearchFunction; + + for (i = 0; i < 0x100; i++) { + h.hash[i] = static_cast(-1); } - h->chain = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h->symbols_num * sizeof(unsigned long), kDynamicSymbolsMsg)); - h->isOriginal = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h->symbols_num * sizeof(unsigned long), kDynamicSymbols2Msg)); + h.chain = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h.symbols_num * sizeof(unsigned long), kDynamicSymbolsMsg)); + h.isOriginal = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h.symbols_num * sizeof(unsigned long), kDynamicSymbols2Msg)); - for (int i = 0; i < h->symbols_num; i++) { - ELF32_Sym *sym = &h->symtab[i]; + for (i = 0; i < h.symbols_num; i++) { + ELF32_Sym *sym = &h.symtab[i]; - sym->st_name = ByteSwap32(sym->st_name); - sym->st_value = ByteSwap32(sym->st_value); - sym->st_size = ByteSwap32(sym->st_size); - sym->st_shndx = ByteSwap16(sym->st_shndx); + sym->st_name = htotul(sym->st_name); + sym->st_value = htotul(sym->st_value); + sym->st_size = htotul(sym->st_size); + sym->st_shndx = htotus(sym->st_shndx); - unsigned long hash = elfhash(h->strtab + sym->st_name); - h->chain[i] = h->hash[hash]; - h->hash[hash] = i; + unsigned long hash = elfhash(h.strtab + sym->st_name); + h.chain[i] = h.hash[hash]; + h.hash[hash] = i; - if (sym->st_shndx == 0 || h->e->e_shnum <= sym->st_shndx) { - h->isOriginal[i] = false; + if (sym->st_shndx == 0 || h.e->e_shnum <= sym->st_shndx) { + h.isOriginal[i] = false; } else { - h->isOriginal[i] = true; + h.isOriginal[i] = true; } } - h->next = hashhead; + h.next = hashhead; if (hashhead) { - hashhead->prev = h; + hashhead->prev = &h; } - h->prev = nullptr; - hashhead = h; - handle = h; + h.prev = nullptr; + hashhead = &h; + handle = &h; } DynamicLoader::~DynamicLoader() { diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h index 5635cc2e5..e52c96a1b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h @@ -54,7 +54,7 @@ class DynamicLoader { // bool IsResolved() {} - // void *ELFAddr(unsigned int offset) {} + void *ELFAddr(unsigned int offset); DynamicLoader(void *d, unsigned int len, DynamicUserCallback pSearchFunction); @@ -253,7 +253,7 @@ enum ELF32_SHN_TYPES { SHN_UNDEF = 0, SHN_MIPS_ACCOMON = 65280, SHN_ABS = 65521, // total size: 0x430 struct HashPointer { - // void *operator new(size_t size) {} + void *operator new(size_t size); // void *operator new(size_t size, const char *msg) {} @@ -277,9 +277,7 @@ struct HashPointer { return *mpDynamicLoader; } - HashPointer(DynamicLoader *pDL) { - mpDynamicLoader = pDL; - } + HashPointer(DynamicLoader *pDL); ~HashPointer() {} From 8f264f60ecca46ed000a9a016b3320df720b0bab Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:15:14 +0100 Subject: [PATCH 209/372] 79.8%: inline DeltaQ quantization helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h | 51 ++++- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 211 +++++++-------------- 2 files changed, 111 insertions(+), 151 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h index b0c56d7ef..45c4fd0d1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h @@ -10,6 +10,8 @@ namespace EAGL4Anim { +inline void DeltaQRecoverW(int signBit, UMath::Vector4 &q); + // total size: 0x18 struct DeltaQMinRangef { UMath::Vector3 mMin; // offset 0x0, size 0xC @@ -18,7 +20,16 @@ struct DeltaQMinRangef { // total size: 0xC struct DeltaQMinRange { - void UnQuantize(DeltaQMinRangef &minRangef) {} + void UnQuantize(DeltaQMinRangef &minRangef) { + const float RangeScale16Bit = 3.0518044e-5f; + + minRangef.mMin.x = mMin[0] * RangeScale16Bit - 1.0f; + minRangef.mMin.y = mMin[1] * RangeScale16Bit - 1.0f; + minRangef.mMin.z = mMin[2] * RangeScale16Bit - 1.0f; + minRangef.mRange.x = mRange[0] * 9.6119827e-7f; + minRangef.mRange.y = mRange[1] * 4.7871444e-7f; + minRangef.mRange.z = mRange[2] * 4.7871444e-7f; + } unsigned short mMin[3]; // offset 0x0, size 0x6 unsigned short mRange[3]; // offset 0x6, size 0x6 @@ -26,7 +37,12 @@ struct DeltaQMinRange { // total size: 0x3 struct DeltaQDelta { - void UnQuantize(const DeltaQMinRangef &minRangef, UMath::Vector4 &q) {} + void UnQuantize(const DeltaQMinRangef &minRangef, UMath::Vector4 &q) { + q.x = minRangef.mMin.x + minRangef.mRange.x * mX; + q.y = minRangef.mMin.y + minRangef.mRange.y * mY; + q.z = minRangef.mMin.z + minRangef.mRange.z * mZ; + DeltaQRecoverW(mW, q); + } unsigned char mX : 7; // offset 0x0, size 0x1 unsigned char mW : 1; // offset 0x0, size 0x1 @@ -37,8 +53,13 @@ struct DeltaQDelta { // total size: 0x6 struct DeltaQPhysical { void UnQuantize(UMath::Vector4 &q) { - // const float RangeScale15Bit; - // const float RangeScale16Bit; + const float RangeScale16Bit = 3.0518044e-5f; + const float RangeScale15Bit = 6.1037019e-5f; + + q.x = mX * RangeScale15Bit - 1.0f; + q.y = mY * RangeScale16Bit - 1.0f; + q.z = mZ * RangeScale16Bit - 1.0f; + DeltaQRecoverW(mW, q); } unsigned short mX : 15; // offset 0x0, size 0x2 @@ -53,6 +74,21 @@ inline void DeltaQRecoverW(int signBit, UMath::Vector4 &q) { { float len; } + + ndotn = q.x * q.x + q.y * q.y + q.z * q.z; + if (ndotn <= 1.0f) { + q.w = FastSqrt(1.0f - ndotn); + if (signBit > 0) { + q.w = -q.w; + } + } else { + float len = FastSqrt(ndotn); + + q.x /= len; + q.y /= len; + q.z /= len; + q.w = 0.0f; + } } // total size: 0x14 @@ -87,8 +123,7 @@ struct DeltaQ : public AnimMemoryMap { void GetArrays(DeltaQMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQPhysical *&constPhysical) {} int GetBinSize() const { - // TODO - return AlignSize2(3 * ((GetBinLength() - 1) * mNumBones)); + return AlignSize2((mNumBones * sizeof(DeltaQPhysical)) + ((GetBinLength() - 1) * mNumBones * sizeof(DeltaQDelta))); } DeltaQMinRange *GetMinRange() { @@ -106,7 +141,9 @@ struct DeltaQ : public AnimMemoryMap { return reinterpret_cast(binData); } - DeltaQDelta *GetDelta(unsigned char *binData, int deltaIdx) {} + DeltaQDelta *GetDelta(unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[mNumBones * sizeof(DeltaQPhysical) + deltaIdx * mNumBones * sizeof(DeltaQDelta)]); + } unsigned char *GetConstBoneIdx(); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 4bb0aa6f2..74225d605 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -44,21 +44,6 @@ namespace { static const float kFloatZero = 0.0f; static const float kFloatOne = 1.0f; -static const float kRangeScale16Bit = 3.0518044e-5f; -static const float kRangeScale15Bit = 6.1037019e-5f; -static const float kRangeScale8Bit = 7.8431377e-3f; -static const float kRangeScale7Bit = 1.5748032e-2f; -static const float kRangeScale16To7Bit = 9.6119827e-7f; -static const float kRangeScale16To8Bit = 4.7871444e-7f; - -static inline int GetFrameDeltaSize(const DeltaQ *deltaQ) { - return deltaQ->mNumBones * sizeof(DeltaQDelta); -} - -static inline int GetBinSize(const DeltaQ *deltaQ) { - return static_cast(AlignSize2((deltaQ->mNumBones * sizeof(DeltaQPhysical)) + - ((deltaQ->GetBinLength() - 1) * GetFrameDeltaSize(deltaQ)))); -} static inline DeltaQMinRange *GetMinRanges(DeltaQ *deltaQ) { return deltaQ->GetMinRange(); @@ -69,7 +54,7 @@ static inline unsigned char *GetBinStart(DeltaQ *deltaQ) { } static inline unsigned char *GetBin(DeltaQ *deltaQ, int binIdx) { - return &GetBinStart(deltaQ)[binIdx * GetBinSize(deltaQ)]; + return &GetBinStart(deltaQ)[binIdx * deltaQ->GetBinSize()]; } static inline DeltaQPhysical *GetPhysical(unsigned char *binData) { @@ -77,76 +62,7 @@ static inline DeltaQPhysical *GetPhysical(unsigned char *binData) { } static inline DeltaQDelta *GetDelta(DeltaQ *deltaQ, unsigned char *binData, int deltaIdx) { - return reinterpret_cast(&binData[deltaQ->mNumBones * sizeof(DeltaQPhysical) + - (deltaIdx * GetFrameDeltaSize(deltaQ))]); -} - -static inline unsigned char *GetConstBoneIdx(DeltaQ *deltaQ) { - const int binSize = GetBinSize(deltaQ); - int numBins = deltaQ->mNumKeys >> deltaQ->GetBinLengthPower(); - unsigned char *s = &GetBin(deltaQ, 0)[binSize * numBins]; - int r = deltaQ->mNumKeys & deltaQ->GetBinLengthModMask(); - - if (r > 0) { - s = reinterpret_cast( - AlignSize2(reinterpret_cast(s + (deltaQ->mNumBones * sizeof(DeltaQPhysical)) + - ((r - 1) * GetFrameDeltaSize(deltaQ))))); - } - if (deltaQ->mNumBones == 0) { - s = reinterpret_cast(AlignSize2(reinterpret_cast(s))); - } - - return s; -} - -static inline DeltaQPhysical *GetConstPhysical(DeltaQ *deltaQ) { - return reinterpret_cast(AlignSize2(reinterpret_cast(&GetConstBoneIdx(deltaQ)[deltaQ->mNumConstBones]))); -} - -static inline void RecoverW(int signBit, UMath::Vector4 &q) { - float ndotn = q.x * q.x + q.y * q.y + q.z * q.z; - - if (ndotn <= kFloatOne) { - q.w = FastSqrt(kFloatOne - ndotn); - if (signBit > 0) { - q.w = -q.w; - } - } else { - float len = FastSqrt(ndotn); - - q.x /= len; - q.y /= len; - q.z /= len; - q.w = kFloatZero; - } -} - -static inline void DecodePhysical(const DeltaQPhysical &physical, UMath::Vector4 &q) { - q.x = physical.mX * kRangeScale15Bit - kFloatOne; - q.y = physical.mY * kRangeScale16Bit - kFloatOne; - q.z = physical.mZ * kRangeScale16Bit - kFloatOne; - RecoverW(physical.mW, q); -} - -static inline void DecodeMinRange(const DeltaQMinRange &minRange, DeltaQMinRangef &minRangef) { - minRangef.mMin.x = minRange.mMin[0] * kRangeScale16Bit - kFloatOne; - minRangef.mMin.y = minRange.mMin[1] * kRangeScale16Bit - kFloatOne; - minRangef.mMin.z = minRange.mMin[2] * kRangeScale16Bit - kFloatOne; - - minRangef.mRange.x = minRange.mRange[0] * kRangeScale16To7Bit; - minRangef.mRange.y = minRange.mRange[1] * kRangeScale16To8Bit; - minRangef.mRange.z = minRange.mRange[2] * kRangeScale16To8Bit; -} - -static inline void DecodeDelta(const DeltaQMinRange &minRange, const DeltaQDelta &delta, UMath::Vector4 &q) { - DeltaQMinRangef minRangef; - - DecodeMinRange(minRange, minRangef); - - q.x = minRangef.mMin.x + minRangef.mRange.x * delta.mX; - q.y = minRangef.mMin.y + minRangef.mRange.y * delta.mY; - q.z = minRangef.mMin.z + minRangef.mRange.z * delta.mZ; - RecoverW(delta.mW, q); + return deltaQ->GetDelta(binData, deltaIdx); } static inline float *GetOutputQuat(float *sqt, unsigned char boneIdx) { @@ -184,7 +100,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = AlignSize2(deltaQ->mNumBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); + mBinSize = deltaQ->GetBinSize(); if (deltaQ->mNumBones != 0) { UMath::Vector4 *prevQs = @@ -245,7 +161,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DecodePhysical(floorPhys[ibone], mPrevQs[ibone]); + floorPhys[ibone].UnQuantize(mPrevQs[ibone]); } prevDeltaIdx = 0; } else { @@ -257,12 +173,14 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 delta; - - DecodeDelta(mMinRanges[ibone], *floorDelta, delta); - mPrevQs[ibone].x += delta.x; - mPrevQs[ibone].y += delta.y; - mPrevQs[ibone].z += delta.z; + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; floorDelta++; } } @@ -271,12 +189,14 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 delta; - - DecodeDelta(mMinRanges[ibone], *floorDelta, delta); - mPrevQs[ibone].x -= delta.x; - mPrevQs[ibone].y -= delta.y; - mPrevQs[ibone].z -= delta.z; + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x -= deltaf.x; + mPrevQs[ibone].y -= deltaf.y; + mPrevQs[ibone].z -= deltaf.z; floorDelta++; } } @@ -284,13 +204,13 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (floorDeltaIdx == 0) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - RecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); + DeltaQRecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); } } else { DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, floorDeltaIdx - 1); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - RecoverW(floorDelta->mW, mPrevQs[ibone]); + DeltaQRecoverW(floorDelta->mW, mPrevQs[ibone]); floorDelta++; } } @@ -331,7 +251,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { float *ceilQ = reinterpret_cast(&ceilq); float *out = GetOutputQuat(sqt, boneIdxs[ibone]); - DecodePhysical(ceilPhys[ibone], ceilq); + ceilPhys[ibone].UnQuantize(ceilq); if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; @@ -357,14 +277,16 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 delta; + UMath::Vector4 deltaf; UMath::Vector4 ceilq; + DeltaQMinRangef minRangef; - DecodeDelta(mMinRanges[ibone], *ceilDelta, delta); - ceilq.x = mPrevQs[ibone].x + delta.x; - ceilq.y = mPrevQs[ibone].y + delta.y; - ceilq.z = mPrevQs[ibone].z + delta.z; - RecoverW(ceilDelta->mW, ceilq); + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta->UnQuantize(minRangef, deltaf); + ceilq.x = mPrevQs[ibone].x + deltaf.x; + ceilq.y = mPrevQs[ibone].y + deltaf.y; + ceilq.z = mPrevQs[ibone].z + deltaf.z; + DeltaQRecoverW(ceilDelta->mW, ceilq); FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), GetOutputQuat(sqt, boneIdxs[ibone])); @@ -379,12 +301,10 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (deltaQ->mNumConstBones != 0) { for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { - float *out = GetOutputQuat(sqt, mConstBoneIdxs[ibone]); + UMath::Vector4 constq; - out[0] = mConstPhysical[ibone].mX * kRangeScale15Bit - kFloatOne; - out[1] = mConstPhysical[ibone].mY * kRangeScale16Bit - kFloatOne; - out[2] = mConstPhysical[ibone].mZ * kRangeScale16Bit - kFloatOne; - RecoverW(mConstPhysical[ibone].mW, *reinterpret_cast(out)); + mConstPhysical[ibone].UnQuantize(constq); + *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; } } @@ -401,7 +321,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = AlignSize2(deltaQ->mNumBones * ((((1 << deltaQ->mBinLengthPower) - 1) * 3) + 6)); + mBinSize = deltaQ->GetBinSize(); if (deltaQ->mNumBones != 0) { UMath::Vector4 *prevQs = @@ -462,7 +382,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - DecodePhysical(floorPhys[ibone], mPrevQs[ibone]); + floorPhys[ibone].UnQuantize(mPrevQs[ibone]); } } prevDeltaIdx = 0; @@ -476,12 +396,14 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 delta; - - DecodeDelta(mMinRanges[ibone], *floorDelta, delta); - mPrevQs[ibone].x += delta.x; - mPrevQs[ibone].y += delta.y; - mPrevQs[ibone].z += delta.z; + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; } floorDelta++; } @@ -492,12 +414,14 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 delta; - - DecodeDelta(mMinRanges[ibone], *floorDelta, delta); - mPrevQs[ibone].x -= delta.x; - mPrevQs[ibone].y -= delta.y; - mPrevQs[ibone].z -= delta.z; + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x -= deltaf.x; + mPrevQs[ibone].y -= deltaf.y; + mPrevQs[ibone].z -= deltaf.z; } floorDelta++; } @@ -507,7 +431,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (floorDeltaIdx == 0) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - RecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); + DeltaQRecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); } } } else { @@ -515,7 +439,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - RecoverW(floorDelta->mW, mPrevQs[ibone]); + DeltaQRecoverW(floorDelta->mW, mPrevQs[ibone]); } floorDelta++; } @@ -558,10 +482,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq float *ceilQ = reinterpret_cast(&ceilq); float *out = GetOutputQuat(sqt, boneIdxs[ibone]); - ceilQ[0] = ceilPhys[ibone].mX * kRangeScale15Bit - kFloatOne; - ceilQ[1] = ceilPhys[ibone].mY * kRangeScale16Bit - kFloatOne; - ceilQ[2] = ceilPhys[ibone].mZ * kRangeScale16Bit - kFloatOne; - RecoverW(ceilPhys[ibone].mW, ceilq); + ceilPhys[ibone].UnQuantize(ceilq); if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; @@ -589,17 +510,19 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 delta; + UMath::Vector4 deltaf; UMath::Vector4 ceilq; float *prevQ = reinterpret_cast(&mPrevQs[ibone]); float *ceilQ = reinterpret_cast(&ceilq); float *out = GetOutputQuat(sqt, boneIdxs[ibone]); + DeltaQMinRangef minRangef; - DecodeDelta(mMinRanges[ibone], *ceilDelta, delta); - ceilq.x = mPrevQs[ibone].x + delta.x; - ceilq.y = mPrevQs[ibone].y + delta.y; - ceilq.z = mPrevQs[ibone].z + delta.z; - RecoverW(ceilDelta->mW, ceilq); + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta->UnQuantize(minRangef, deltaf); + ceilq.x = mPrevQs[ibone].x + deltaf.x; + ceilq.y = mPrevQs[ibone].y + deltaf.y; + ceilq.z = mPrevQs[ibone].z + deltaf.z; + DeltaQRecoverW(ceilDelta->mW, ceilq); if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; @@ -638,7 +561,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (boneMask->GetBone(mConstBoneIdxs[ibone])) { UMath::Vector4 constq; - DecodePhysical(mConstPhysical[ibone], constq); + mConstPhysical[ibone].UnQuantize(constq); *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; } } @@ -662,9 +585,9 @@ void FnDeltaQ::InitBuffersAsRequired() { mMinRanges = GetMinRanges(deltaQ); mBins = GetBinStart(deltaQ); - mBinSize = GetBinSize(deltaQ); - mConstBoneIdxs = GetConstBoneIdx(deltaQ); - mConstPhysical = GetConstPhysical(deltaQ); + mBinSize = deltaQ->GetBinSize(); + mConstBoneIdxs = deltaQ->GetConstBoneIdx(); + mConstPhysical = deltaQ->GetConstPhysical(); if (deltaQ->mNumBones != 0) { mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); From 173e5bfd22704248cbe8259da91f8f927522ad23 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:28:40 +0100 Subject: [PATCH 210/372] 79.9%: reshape DynamicLoader::Resolve Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Src/EAGL4Anim/eagl4supportconspool.h | 4 +- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 183 ++++++++++-------- 2 files changed, 106 insertions(+), 81 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h index 6376dfb62..67a390829 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h @@ -28,7 +28,9 @@ typedef void (*RuntimeAllocDestructor)(void *, int); // TODO wrong namespace // total size: 0x10 struct RuntimeAllocDestructorEntry { - // void *operator new(size_t size) {} + void *operator new(size_t size) { + return EAGL4Internal::EAGL4Malloc(size, nullptr); + } // void *operator new(size_t size, const char *msg) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 0e2b4da59..dc354cd01 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -135,155 +135,178 @@ bool DynamicLoader::DoVersionCheck() { } void DynamicLoader::Resolve() { - HashPointer *h = reinterpret_cast(handle); + int i; + int j; + HashPointer *h; + ELFSectionHeader *sheader; + ELFHeader *e; + const int MAX_UNRESOLVED_ERRORS = 32; + bool unresolvedSymbolError; + char *unresolvedList[32]; + int numUnresolved; + + h = reinterpret_cast(handle); if (!h || h->resolved) { return; } + e = h->e; h->resolved = true; + sheader = h->sections; + unresolvedSymbolError = false; + numUnresolved = 0; + +retry: + ; - ELFSectionHeader *sections = h->sections; - int unresolvedCount = 0; - int unresolvedSeen[32]; - int sectionCount = h->e->e_shnum; + for (i = 0; i < e->e_shnum; i++) { + int relocations; + char *patchbase; + ELF32_Rel *r; + ELF32_Sym *symtab; - for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { - ELFSectionHeader *section = §ions[sectionIndex]; - if (section->sh_type != SHT_REL) { + if (sheader[i].sh_type != SHT_REL) { continue; } - ELF32_Rel *rel = reinterpret_cast(section->sh_voffset); - int relCount = static_cast(section->sh_size); - if (relCount < 0) { - relCount += 7; + relocations = static_cast(sheader[i].sh_size); + if (relocations < 0) { + relocations += 7; } - relCount >>= 3; - - ELF32_Sym *symtab = reinterpret_cast(section->sh_vlink); - char *targetBase = reinterpret_cast(section->sh_vinfo); - - for (int relIndex = 0; relIndex < relCount; relIndex++) { - rel[relIndex].r_offset = ByteSwap32(rel[relIndex].r_offset); - rel[relIndex].r_info = ByteSwap32(rel[relIndex].r_info); - - ELF32_Sym *sym = &symtab[rel[relIndex].r_info >> 8]; - int symbolBase = 0; + patchbase = reinterpret_cast(sheader[i].sh_vinfo); + r = reinterpret_cast(sheader[i].sh_voffset); + symtab = reinterpret_cast(sheader[i].sh_vlink); + relocations >>= 3; + + for (j = 0; j < relocations; j++) { + unsigned int baseaddr = 0; + int iIndex = j; + ELF32_Sym *sym; + unsigned int *patchaddr; + unsigned short *patchaddr16; + + r[j].r_offset = htotul(r[j].r_offset); + r[j].r_info = htotul(r[j].r_info); + sym = &symtab[r[j].r_info >> 8]; while ((sym->st_info & 0xF) < STT_FILE) { - if (sym->st_shndx && sym->st_shndx < h->e->e_shnum) { - symbolBase = reinterpret_cast(sections[sym->st_shndx].sh_voffset) - reinterpret_cast(nullptr); + if (sym->st_shndx && sym->st_shndx < e->e_shnum) { + baseaddr = reinterpret_cast(sheader[sym->st_shndx].sh_voffset); break; } - const char *name = &h->strtab[sym->st_name]; - bool valid = false; - void *resolvedAddr = nullptr; + char *name = &h->strtab[sym->st_name]; + HashPointer *hp; + + for (hp = hashhead; hp; hp = hp->next) { + if (hp != h) { + void *addr = dlsym(hp, name); - for (HashPointer *other = hashhead; other; other = other->next) { - if (other != h) { - resolvedAddr = dlsym(other, name); - if (resolvedAddr) { + if (addr) { sym->st_shndx = 1; sym->st_other = 2; - sym->st_value = reinterpret_cast(resolvedAddr) - reinterpret_cast(sections[1].sh_voffset); - symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; break; } } } - if (resolvedAddr) { + if (hp) { break; } if (h->pSearchFunction) { - resolvedAddr = h->pSearchFunction(name, valid); + bool valid = false; + void *addr = h->pSearchFunction(name, valid); + if (valid) { sym->st_shndx = 1; sym->st_other = 3; - sym->st_value = reinterpret_cast(resolvedAddr) - reinterpret_cast(sections[1].sh_voffset); - symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; break; } } - resolvedAddr = gSymbolPool.Search(name, valid); - if (valid) { - sym->st_shndx = 1; - sym->st_other = 4; - sym->st_value = reinterpret_cast(resolvedAddr) - reinterpret_cast(sections[1].sh_voffset); - symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); - break; + { + bool valid = false; + void *addr = gSymbolPool.Search(name, valid); + + if (valid) { + sym->st_shndx = 1; + sym->st_other = 4; + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; + break; + } } const char *type = GetLoaderSymbolType(name); if (strncmp(gRuntimeAllocType, name, strlen(gRuntimeAllocType)) == 0) { - RuntimeAllocConstructor ctor = gRuntimeAllocConsPool.FindConstructor(type); - if (ctor) { - RuntimeAllocDestructor dtor = gRuntimeAllocConsPool.FindDestructor(type); + const char *stripped_name = name + strlen(gRuntimeAllocType); + RuntimeAllocConstructor c = gRuntimeAllocConsPool.FindConstructor(type); + + if (c) { + RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(type); int auxData = 0; - bool createDestructor = false; - void *runtimeAlloc = - ctor(name + strlen(gRuntimeAllocType), reinterpret_cast(this), auxData, createDestructor, name); - - if (createDestructor && runtimeAlloc) { - RuntimeAllocDestructorEntry *entry = - reinterpret_cast(EAGL4Internal::EAGL4Malloc(sizeof(RuntimeAllocDestructorEntry), nullptr)); - entry->d = dtor; - entry->data = runtimeAlloc; - entry->auxData = auxData; - entry->next = RuntimeAllocDestructors; - RuntimeAllocDestructors = entry; + bool bCallDestructor = false; + void *addr = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, name); + + if (bCallDestructor && addr) { + RuntimeAllocDestructorEntry *de = new RuntimeAllocDestructorEntry(d, addr, auxData); + de->next = RuntimeAllocDestructors; + RuntimeAllocDestructors = de; } sym->st_shndx = 1; sym->st_other = 5; - sym->st_value = reinterpret_cast(runtimeAlloc) - reinterpret_cast(sections[1].sh_voffset); - symbolBase = reinterpret_cast(sections[1].sh_voffset) - reinterpret_cast(nullptr); + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; break; } } - bool alreadySeen = false; - if (unresolvedCount <= 0x1F) { - for (int i = 0; i < unresolvedCount; i++) { - if (name == reinterpret_cast(unresolvedSeen[i])) { - alreadySeen = true; + unresolvedSymbolError = true; + if (numUnresolved < MAX_UNRESOLVED_ERRORS) { + bool found = false; + + for (int k = 0; k < numUnresolved; k++) { + if (name == unresolvedList[k]) { + found = true; break; } } - if (!alreadySeen) { - unresolvedSeen[unresolvedCount++] = reinterpret_cast(name); + + if (!found) { + unresolvedList[numUnresolved++] = name; } } break; } - int value = symbolBase + sym->st_value; - unsigned int *relTarget = reinterpret_cast(targetBase + rel[relIndex].r_offset); - unsigned int relValue = ByteSwap32(*relTarget); + baseaddr += sym->st_value; + patchaddr = reinterpret_cast(patchbase + r[j].r_offset); + *patchaddr = htotul(*patchaddr); + patchaddr16 = reinterpret_cast(patchaddr); - switch (rel[relIndex].r_info & 0xFF) { + switch (r[j].r_info & 0xFF) { case R_MIPS_32: - relValue += value; + *patchaddr += baseaddr; break; case R_MIPS_26: - relValue = (relValue & 0xFC000000) | ((value + ((relValue & 0x03FFFFFF) * 4) & 0x0FFFFFFF) >> 2); + *patchaddr = (*patchaddr & 0xFC000000) | ((baseaddr + ((*patchaddr & 0x03FFFFFF) * 4) & 0x0FFFFFFF) >> 2); break; case R_MIPS_HI16: - *reinterpret_cast(relTarget) = - static_cast(*reinterpret_cast(relTarget) + static_cast(static_cast(value) >> 16)); + *patchaddr16 = static_cast(*patchaddr16 + static_cast(baseaddr >> 16)); continue; case R_MIPS_LO16: - *reinterpret_cast(relTarget) = static_cast(*reinterpret_cast(relTarget) + static_cast(value)); + *patchaddr16 = static_cast(*patchaddr16 + static_cast(baseaddr)); continue; } - - *relTarget = ByteSwap32(relValue); } } From f0856f8ff27624f275b1c943705b594d0b30e759 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:40:35 +0100 Subject: [PATCH 211/372] 80.1%: reorder Skeleton::MirrorPose branches Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 208 +++++++++--------- .../Src/EAGL4Anim/eagl4supportconspool.h | 9 +- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 41 ++-- 3 files changed, 133 insertions(+), 125 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index c1a222cf6..0df0df2bd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -138,132 +138,132 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const BoneMask *mask) { int numBones = GetNumBones(); - if (!mask) { + if (mask) { if (pose == mirrorPose) { - for (int ibone = 0; ibone < numBones; ibone++) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { + if (mask->GetBone(ibone)) { + unsigned int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; - if (ibone < mirrorBone) { - float *dst = &mirrorPose[mirrorBone * 12]; - float *src = &mirrorPose[ibone * 12]; - float value = dst[4]; + if (ibone < mirrorBone) { + float *dst = &mirrorPose[mirrorBone * 12]; + float *src = &mirrorPose[ibone * 12]; + float value = dst[4]; - dst[4] = -src[4]; - src[4] = -value; - value = dst[5]; - dst[5] = -src[5]; - src[5] = -value; - value = dst[6]; - dst[6] = src[6]; - src[6] = value; - value = dst[7]; - dst[7] = src[7]; - src[7] = value; - value = dst[8]; - dst[8] = src[8]; - src[8] = value; - value = dst[9]; - dst[9] = src[9]; - src[9] = value; - value = dst[10]; - dst[10] = -src[10]; - src[10] = -value; - } else if (mirrorBone == ibone) { - float *dst = &mirrorPose[ibone * 12]; + dst[4] = -src[4]; + src[4] = -value; + value = dst[5]; + dst[5] = -src[5]; + src[5] = -value; + value = dst[6]; + dst[6] = src[6]; + src[6] = value; + value = dst[7]; + dst[7] = src[7]; + src[7] = value; + value = dst[8]; + dst[8] = src[8]; + src[8] = value; + value = dst[9]; + dst[9] = src[9]; + src[9] = value; + value = dst[10]; + dst[10] = -src[10]; + src[10] = -value; + } else if (mirrorBone == ibone) { + float *dst = &mirrorPose[ibone * 12]; - dst[4] = -dst[4]; - dst[5] = -dst[5]; - dst[10] = -dst[10]; + dst[4] = -dst[4]; + dst[5] = -dst[5]; + dst[10] = -dst[10]; + } } } } else { - for (int ibone = 0; ibone < numBones; ibone++) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; - float *src = &pose[ibone * 12]; - float *dst = &mirrorPose[mirrorBone * 12]; - - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - dst[4] = src[4]; - dst[5] = -src[5]; - dst[6] = -src[6]; - dst[7] = src[7]; - dst[8] = -src[8]; - dst[9] = src[9]; - dst[10] = src[10]; - dst[11] = src[11]; - - if (!local) { - dst[4] = -dst[4]; - dst[7] = -dst[7]; - } - } - } - } else if (pose == mirrorPose) { - for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { - if (mask->GetBone(ibone)) { - unsigned int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; - - if (ibone < mirrorBone) { + for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { + if (mask->GetBone(ibone)) { + int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; + float *src = &pose[ibone * 12]; float *dst = &mirrorPose[mirrorBone * 12]; - float *src = &mirrorPose[ibone * 12]; - float value = dst[4]; - dst[4] = -src[4]; - src[4] = -value; - value = dst[5]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; dst[5] = -src[5]; - src[5] = -value; - value = dst[6]; - dst[6] = src[6]; - src[6] = value; - value = dst[7]; + dst[6] = -src[6]; dst[7] = src[7]; - src[7] = value; - value = dst[8]; - dst[8] = src[8]; - src[8] = value; - value = dst[9]; + dst[8] = -src[8]; dst[9] = src[9]; - src[9] = value; - value = dst[10]; - dst[10] = -src[10]; - src[10] = -value; - } else if (mirrorBone == ibone) { - float *dst = &mirrorPose[ibone * 12]; + dst[10] = src[10]; + dst[11] = src[11]; - dst[4] = -dst[4]; - dst[5] = -dst[5]; - dst[10] = -dst[10]; + if (!local) { + dst[4] = -dst[4]; + dst[7] = -dst[7]; + } } } } - } else { - for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { - if (mask->GetBone(ibone)) { - int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; - float *src = &pose[ibone * 12]; + } else if (pose == mirrorPose) { + for (int ibone = 0; ibone < numBones; ibone++) { + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + + if (ibone < mirrorBone) { float *dst = &mirrorPose[mirrorBone * 12]; + float *src = &mirrorPose[ibone * 12]; + float value = dst[4]; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - dst[4] = src[4]; + dst[4] = -src[4]; + src[4] = -value; + value = dst[5]; dst[5] = -src[5]; - dst[6] = -src[6]; + src[5] = -value; + value = dst[6]; + dst[6] = src[6]; + src[6] = value; + value = dst[7]; dst[7] = src[7]; - dst[8] = -src[8]; + src[7] = value; + value = dst[8]; + dst[8] = src[8]; + src[8] = value; + value = dst[9]; dst[9] = src[9]; - dst[10] = src[10]; - dst[11] = src[11]; + src[9] = value; + value = dst[10]; + dst[10] = -src[10]; + src[10] = -value; + } else if (mirrorBone == ibone) { + float *dst = &mirrorPose[ibone * 12]; - if (!local) { - dst[4] = -dst[4]; - dst[7] = -dst[7]; - } + dst[4] = -dst[4]; + dst[5] = -dst[5]; + dst[10] = -dst[10]; + } + } + } else { + for (int ibone = 0; ibone < numBones; ibone++) { + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + float *src = &pose[ibone * 12]; + float *dst = &mirrorPose[mirrorBone * 12]; + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = -src[5]; + dst[6] = -src[6]; + dst[7] = src[7]; + dst[8] = -src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + + if (!local) { + dst[4] = -dst[4]; + dst[7] = -dst[7]; } } } diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h index 67a390829..948070d82 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h @@ -28,9 +28,7 @@ typedef void (*RuntimeAllocDestructor)(void *, int); // TODO wrong namespace // total size: 0x10 struct RuntimeAllocDestructorEntry { - void *operator new(size_t size) { - return EAGL4Internal::EAGL4Malloc(size, nullptr); - } + void *operator new(size_t size); // void *operator new(size_t size, const char *msg) {} @@ -46,10 +44,7 @@ struct RuntimeAllocDestructorEntry { // void *operator new(size_t, void *ptr) {} - RuntimeAllocDestructorEntry(RuntimeAllocDestructor d, void *data, int auxData) - : d(d), // - data(data), // - auxData(auxData) {} + RuntimeAllocDestructorEntry(RuntimeAllocDestructor d, void *data, int auxData); RuntimeAllocDestructor d; // offset 0x0, size 0x4 void *data; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index dc354cd01..4484dc4de 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -7,6 +7,15 @@ static void *dlsym(void *handle, const char *name); +inline void *RuntimeAllocDestructorEntry::operator new(size_t size) { + return EAGL4Internal::EAGL4Malloc(size, nullptr); +} + +inline RuntimeAllocDestructorEntry::RuntimeAllocDestructorEntry(RuntimeAllocDestructor d, void *data, int auxData) + : d(d), // + data(data), // + auxData(auxData) {} + namespace EAGL4 { namespace { @@ -35,14 +44,6 @@ static inline unsigned int ByteSwap32(unsigned int value) { return ((value >> 16) & 0xFF) << 8 | (value >> 24) | (((value & 0xFF) << 8 | ((value & 0xFFFF) >> 8)) << 16); } -static inline const char *GetLoaderSymbolType(const char *name) { - const char *type = name + strlen(name); - if (type[1] == 0x7F) { - type += 2; - } - return type + 1; -} - } // namespace inline void *HashPointer::operator new(size_t size) { @@ -242,16 +243,27 @@ void DynamicLoader::Resolve() { } } - const char *type = GetLoaderSymbolType(name); + const char *type = name + strlen(name); + + if (type[1] == 0x7F) { + type += 2; + } + type += 1; if (strncmp(gRuntimeAllocType, name, strlen(gRuntimeAllocType)) == 0) { - const char *stripped_name = name + strlen(gRuntimeAllocType); - RuntimeAllocConstructor c = gRuntimeAllocConsPool.FindConstructor(type); + Symbol s; + const char *stripped_name; + RuntimeAllocConstructor c; + + s.name = name; + s.type = type; + stripped_name = s.name + strlen(gRuntimeAllocType); + c = gRuntimeAllocConsPool.FindConstructor(s.type); if (c) { RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(type); int auxData = 0; bool bCallDestructor = false; - void *addr = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, name); + void *addr = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, s.name); if (bCallDestructor && addr) { RuntimeAllocDestructorEntry *de = new RuntimeAllocDestructorEntry(d, addr, auxData); @@ -271,8 +283,8 @@ void DynamicLoader::Resolve() { if (numUnresolved < MAX_UNRESOLVED_ERRORS) { bool found = false; - for (int k = 0; k < numUnresolved; k++) { - if (name == unresolvedList[k]) { + for (j = 0; j < numUnresolved; j++) { + if (name == unresolvedList[j]) { found = true; break; } @@ -281,6 +293,7 @@ void DynamicLoader::Resolve() { if (!found) { unresolvedList[numUnresolved++] = name; } + j = iIndex; } break; } From 7f54d43553fa19ee6a74d7439d57a394a81c22b4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:45:50 +0100 Subject: [PATCH 212/372] 80.3%: rewrite Skeleton::PoseSQTToGlobal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 55 ++++++++++++---------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index 0df0df2bd..e3ea05107 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -4,43 +4,46 @@ static void MtxMult(EAGL4::Transform *result, const EAGL4::Transform *A, const E namespace EAGL4Anim { +extern void (*MatrixMultiply)(EAGL4::Transform *, const EAGL4::Transform *, const EAGL4::Transform *); + void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask *mask) { - int numBones = GetNumBones(); + BoneData *bones = GetBoneData(); + int n = GetNumBones(); - if (!mask) { - for (int i = 0; i < numBones; i++) { - float *bonePose = pose; - float *mat = output[i].m.GetElements(); + if (mask) { + for (int i = 0; i < n; i++) { + if (mask->GetBone(i)) { + int p = bones[i].mParentIdx; + float *poseData = pose; + float *mat = output[i].m.GetElements(); - output[i].BuildSQT(bonePose[0], bonePose[1], bonePose[2], bonePose[4], bonePose[5], bonePose[6], bonePose[7], bonePose[8], - bonePose[9], bonePose[10]); - mat[0] *= bonePose[3]; - mat[4] *= bonePose[3]; - mat[8] *= bonePose[3]; + output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], + poseData[9], poseData[10]); + mat[0] *= poseData[3]; + mat[4] *= poseData[3]; + mat[8] *= poseData[3]; - int parentIdx = GetBoneData(i).mParentIdx; - if (parentIdx > -1) { - MtxMult(&output[i], &output[parentIdx], &output[i]); + if (p > -1) { + MatrixMultiply(&output[i], &output[p], &output[i]); + } } pose += 12; } } else { - for (int i = 0; i < numBones; i++) { - if (mask->GetBone(i)) { - float *bonePose = pose; - float *mat = output[i].m.GetElements(); + for (int i = 0; i < n; i++) { + int p = bones[i].mParentIdx; + float *poseData = pose; + float *mat = output[i].m.GetElements(); - output[i].BuildSQT(bonePose[0], bonePose[1], bonePose[2], bonePose[4], bonePose[5], bonePose[6], bonePose[7], bonePose[8], - bonePose[9], bonePose[10]); - mat[0] *= bonePose[3]; - mat[4] *= bonePose[3]; - mat[8] *= bonePose[3]; + output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], + poseData[9], poseData[10]); + mat[0] *= poseData[3]; + mat[4] *= poseData[3]; + mat[8] *= poseData[3]; - int parentIdx = GetBoneData(i).mParentIdx; - if (parentIdx > -1) { - MtxMult(&output[i], &output[parentIdx], &output[i]); - } + if (p > -1) { + MatrixMultiply(&output[i], &output[p], &output[i]); } pose += 12; From 00fa1cf152fecdb842ffe59ad895cdb5b5d0a0ef Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:46:57 +0100 Subject: [PATCH 213/372] 80.4%: refine Skeleton::PoseSQTToGlobal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index e3ea05107..4957c2644 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -7,14 +7,14 @@ namespace EAGL4Anim { extern void (*MatrixMultiply)(EAGL4::Transform *, const EAGL4::Transform *, const EAGL4::Transform *); void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask *mask) { - BoneData *bones = GetBoneData(); - int n = GetNumBones(); - if (mask) { + float *poseData = pose; + BoneData *bones = GetBoneData(); + int n = GetNumBones(); + for (int i = 0; i < n; i++) { if (mask->GetBone(i)) { int p = bones[i].mParentIdx; - float *poseData = pose; float *mat = output[i].m.GetElements(); output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], @@ -28,12 +28,15 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * } } - pose += 12; + poseData += 12; } } else { + float *poseData = pose; + BoneData *bones = GetBoneData(); + int n = GetNumBones(); + for (int i = 0; i < n; i++) { int p = bones[i].mParentIdx; - float *poseData = pose; float *mat = output[i].m.GetElements(); output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], @@ -46,7 +49,7 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * MatrixMultiply(&output[i], &output[p], &output[i]); } - pose += 12; + poseData += 12; } } } From 328995d865f48647325b8e423b12ab687831baec Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:49:00 +0100 Subject: [PATCH 214/372] 80.5%: advance Skeleton::PoseSQTToGlobal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index 4957c2644..29ac3cf75 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -14,7 +14,6 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * for (int i = 0; i < n; i++) { if (mask->GetBone(i)) { - int p = bones[i].mParentIdx; float *mat = output[i].m.GetElements(); output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], @@ -23,7 +22,8 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * mat[4] *= poseData[3]; mat[8] *= poseData[3]; - if (p > -1) { + int p = bones[i].mParentIdx; + if (p >= 0) { MatrixMultiply(&output[i], &output[p], &output[i]); } } @@ -36,7 +36,6 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * int n = GetNumBones(); for (int i = 0; i < n; i++) { - int p = bones[i].mParentIdx; float *mat = output[i].m.GetElements(); output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], @@ -45,7 +44,8 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * mat[4] *= poseData[3]; mat[8] *= poseData[3]; - if (p > -1) { + int p = bones[i].mParentIdx; + if (p >= 0) { MatrixMultiply(&output[i], &output[p], &output[i]); } From 28560a229255b852f250bf67af792385b5b9d06f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:53:37 +0100 Subject: [PATCH 215/372] 80.6%: advance Skeleton::MirrorPose Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 49 ++++++++++------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index 29ac3cf75..b35a60c59 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -146,9 +146,9 @@ void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const Bone if (mask) { if (pose == mirrorPose) { - for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { + for (int ibone = 0; ibone < numBones; ibone++) { if (mask->GetBone(ibone)) { - unsigned int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; if (ibone < mirrorBone) { float *dst = &mirrorPose[mirrorBone * 12]; @@ -185,29 +185,22 @@ void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const Bone } } } else { - for (unsigned int ibone = 0; static_cast(ibone) < numBones; ibone++) { + for (int ibone = 0; ibone < numBones; ibone++) { if (mask->GetBone(ibone)) { - int mirrorBone = GetBoneData(static_cast(ibone)).mLeftRightIdx; + int mirrorBone = GetBoneData(ibone).mLeftRightIdx; float *src = &pose[ibone * 12]; float *dst = &mirrorPose[mirrorBone * 12]; dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; - dst[3] = src[3]; - dst[4] = src[4]; + dst[4] = -src[4]; dst[5] = -src[5]; - dst[6] = -src[6]; + dst[6] = src[6]; dst[7] = src[7]; - dst[8] = -src[8]; + dst[8] = src[8]; dst[9] = src[9]; - dst[10] = src[10]; - dst[11] = src[11]; - - if (!local) { - dst[4] = -dst[4]; - dst[7] = -dst[7]; - } + dst[10] = -src[10]; } } } @@ -257,22 +250,26 @@ void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const Bone dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; - dst[3] = src[3]; - dst[4] = src[4]; + dst[4] = -src[4]; dst[5] = -src[5]; - dst[6] = -src[6]; + dst[6] = src[6]; dst[7] = src[7]; - dst[8] = -src[8]; + dst[8] = src[8]; dst[9] = src[9]; - dst[10] = src[10]; - dst[11] = src[11]; - - if (!local) { - dst[4] = -dst[4]; - dst[7] = -dst[7]; - } + dst[10] = -src[10]; } } + + if (!local) { + UMath::Vector4 quat = *reinterpret_cast(&mirrorPose[4]); + + mirrorPose[4] = quat.z; + mirrorPose[5] = quat.w; + mirrorPose[6] = -quat.x; + mirrorPose[10] = -mirrorPose[10]; + mirrorPose[8] = -mirrorPose[8]; + mirrorPose[7] = -quat.y; + } } }; // namespace EAGL4Anim From c371ba2260b6517512992dcdf3c538ba2dbab4eb Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:56:05 +0100 Subject: [PATCH 216/372] 80.8%: rewrite Skeleton::GetStillPose Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 88 +++++++++++----------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index b35a60c59..a247c5a5e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -58,54 +58,16 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { int numBones = GetNumBones(); const float *invBoneScales = GetInvBoneScales(); - if (!mask) { - if (!invBoneScales) { - for (int i = 0; i < numBones; i++) { - const BoneData &bone = GetBoneData(i); - - pose[0] = bone.mS.x; - pose[1] = bone.mS.y; - pose[2] = bone.mS.z; - pose[3] = 1.0f; - pose[4] = bone.mQ.x; - pose[5] = bone.mQ.y; - pose[6] = bone.mQ.z; - pose[7] = bone.mQ.w; - pose[8] = bone.mT.x; - pose[9] = bone.mT.y; - pose[10] = bone.mT.z; - pose[11] = 1.0f; - pose += 12; - } - } else { + if (mask) { + if (invBoneScales) { for (int i = 0; i < numBones; i++) { - const BoneData &bone = GetBoneData(i); - - pose[0] = bone.mS.x; - pose[1] = bone.mS.y; - pose[2] = bone.mS.z; - pose[3] = invBoneScales[i]; - pose[4] = bone.mQ.x; - pose[5] = bone.mQ.y; - pose[6] = bone.mQ.z; - pose[7] = bone.mQ.w; - pose[8] = bone.mT.x; - pose[9] = bone.mT.y; - pose[10] = bone.mT.z; - pose[11] = 1.0f; - pose += 12; - } - } - } else { - if (!invBoneScales) { - for (unsigned int i = 0; i < static_cast(numBones); i++) { if (mask->GetBone(i)) { - const BoneData &bone = GetBoneData(static_cast(i)); + const BoneData &bone = GetBoneData(i); pose[0] = bone.mS.x; pose[1] = bone.mS.y; pose[2] = bone.mS.z; - pose[3] = 1.0f; + pose[3] = invBoneScales[i]; pose[4] = bone.mQ.x; pose[5] = bone.mQ.y; pose[6] = bone.mQ.z; @@ -118,14 +80,14 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { pose += 12; } } else { - for (unsigned int i = 0; i < static_cast(numBones); i++) { + for (int i = 0; i < numBones; i++) { if (mask->GetBone(i)) { - const BoneData &bone = GetBoneData(static_cast(i)); + const BoneData &bone = GetBoneData(i); pose[0] = bone.mS.x; pose[1] = bone.mS.y; + pose[3] = 1.0f; pose[2] = bone.mS.z; - pose[3] = invBoneScales[i]; pose[4] = bone.mQ.x; pose[5] = bone.mQ.y; pose[6] = bone.mQ.z; @@ -138,6 +100,42 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { pose += 12; } } + } else if (invBoneScales) { + for (int i = 0; i < numBones; i++) { + const BoneData &bone = GetBoneData(i); + + pose[0] = bone.mS.x; + pose[1] = bone.mS.y; + pose[2] = bone.mS.z; + pose[3] = invBoneScales[i]; + pose[4] = bone.mQ.x; + pose[5] = bone.mQ.y; + pose[6] = bone.mQ.z; + pose[7] = bone.mQ.w; + pose[8] = bone.mT.x; + pose[9] = bone.mT.y; + pose[10] = bone.mT.z; + pose[11] = 1.0f; + pose += 12; + } + } else { + for (int i = 0; i < numBones; i++) { + const BoneData &bone = GetBoneData(i); + + pose[0] = bone.mS.x; + pose[1] = bone.mS.y; + pose[3] = 1.0f; + pose[2] = bone.mS.z; + pose[4] = bone.mQ.x; + pose[5] = bone.mQ.y; + pose[6] = bone.mQ.z; + pose[7] = bone.mQ.w; + pose[8] = bone.mT.x; + pose[9] = bone.mT.y; + pose[10] = bone.mT.z; + pose[11] = 1.0f; + pose += 12; + } } } From 1fed29aa8d2f3bd69df79c70ba8f8e865cc4208f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 21:57:22 +0100 Subject: [PATCH 217/372] 80.9%: refine Skeleton::GetStillPose Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index a247c5a5e..47b4e0f2e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -56,10 +56,9 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { int numBones = GetNumBones(); - const float *invBoneScales = GetInvBoneScales(); if (mask) { - if (invBoneScales) { + if (GetInvBoneScales()) { for (int i = 0; i < numBones; i++) { if (mask->GetBone(i)) { const BoneData &bone = GetBoneData(i); @@ -67,7 +66,7 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { pose[0] = bone.mS.x; pose[1] = bone.mS.y; pose[2] = bone.mS.z; - pose[3] = invBoneScales[i]; + pose[3] = GetInvBoneScales()[i]; pose[4] = bone.mQ.x; pose[5] = bone.mQ.y; pose[6] = bone.mQ.z; @@ -100,14 +99,14 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { pose += 12; } } - } else if (invBoneScales) { + } else if (GetInvBoneScales()) { for (int i = 0; i < numBones; i++) { const BoneData &bone = GetBoneData(i); pose[0] = bone.mS.x; pose[1] = bone.mS.y; pose[2] = bone.mS.z; - pose[3] = invBoneScales[i]; + pose[3] = GetInvBoneScales()[i]; pose[4] = bone.mQ.x; pose[5] = bone.mQ.y; pose[6] = bone.mQ.z; From f5bffa29e34249dfc31d91f705a5acf66ee19b2e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 22:46:18 +0100 Subject: [PATCH 218/372] 81.0%: synthesize trivial delta channel destructors --- src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h | 12 - .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 14 +- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 14 +- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 372 ++++++++++-------- 4 files changed, 220 insertions(+), 192 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h index 94da14798..cd72222e7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h @@ -136,9 +136,6 @@ class FnDeltaLerpChan : public FnDeltaChan { mType = AnimTypeId::ANIM_DELTALERP; } - // Overrides: FnAnimSuper - ~FnDeltaLerpChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; @@ -180,9 +177,6 @@ class FnDeltaQuatChan : public FnDeltaChan { mType = AnimTypeId::ANIM_DELTAQUAT; } - // Overrides: FnAnimSuper - ~FnDeltaQuatChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; @@ -356,9 +350,6 @@ class FnKeyLerpChan : public FnKeyDeltaChan { mType = AnimTypeId::ANIM_KEYLERP; } - // Overrides: FnAnimSuper - ~FnKeyLerpChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; @@ -394,9 +385,6 @@ class FnKeyQuatChan : public FnKeyDeltaChan { mType = AnimTypeId::ANIM_KEYQUAT; } - // Overrides: FnAnimSuper - ~FnKeyQuatChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 0b2ef68a0..01804af49 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -328,15 +328,23 @@ void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { } float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { - float length = endTime - startTime; + float len = endTime - startTime; + float tmp; + int n; if (t < startTime) { - return endTime - ((startTime - t) - static_cast(static_cast((startTime - t) / length)) * length); + tmp = startTime - t; + n = FloatToInt(tmp / len); + tmp = tmp - static_cast(n) * len; + return endTime - tmp; } if (t < endTime) { return t; } - return startTime + ((t - endTime) - static_cast(static_cast((t - endTime) / length)) * length); + tmp = t - endTime; + n = FloatToInt(tmp / len); + tmp = tmp - static_cast(n) * len; + return startTime + tmp; } int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index b13cd996a..dc8065049 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -191,15 +191,23 @@ float FnTurnBlender::GetFrequency() const { } float FnTurnBlender::CycleTime(float t, float startTime, float endTime) const { - float length = endTime - startTime; + float len = endTime - startTime; + float tmp; + int n; if (t < startTime) { - return endTime - ((startTime - t) - static_cast(static_cast((startTime - t) / length)) * length); + tmp = startTime - t; + n = FloatToInt(tmp / len); + tmp = tmp - static_cast(n) * len; + return endTime - tmp; } if (t < endTime) { return t; } - return startTime + ((t - endTime) - static_cast(static_cast((t - endTime) / length)) * length); + tmp = t - endTime; + n = FloatToInt(tmp / len); + tmp = tmp - static_cast(n) * len; + return startTime + tmp; } int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index 47b4e0f2e..8377cb65c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -8,21 +8,21 @@ extern void (*MatrixMultiply)(EAGL4::Transform *, const EAGL4::Transform *, cons void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask *mask) { if (mask) { + int i; + int p; float *poseData = pose; BoneData *bones = GetBoneData(); int n = GetNumBones(); - for (int i = 0; i < n; i++) { + for (i = 0; i < n; i++) { if (mask->GetBone(i)) { - float *mat = output[i].m.GetElements(); - output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], poseData[9], poseData[10]); - mat[0] *= poseData[3]; - mat[4] *= poseData[3]; - mat[8] *= poseData[3]; + output[i].m.v0.x *= poseData[3]; + output[i].m.v1.x *= poseData[3]; + output[i].m.v2.x *= poseData[3]; - int p = bones[i].mParentIdx; + p = bones[i].mParentIdx; if (p >= 0) { MatrixMultiply(&output[i], &output[p], &output[i]); } @@ -31,20 +31,20 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * poseData += 12; } } else { + int i; + int p; float *poseData = pose; BoneData *bones = GetBoneData(); int n = GetNumBones(); - for (int i = 0; i < n; i++) { - float *mat = output[i].m.GetElements(); - + for (i = 0; i < n; i++) { output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], poseData[9], poseData[10]); - mat[0] *= poseData[3]; - mat[4] *= poseData[3]; - mat[8] *= poseData[3]; + output[i].m.v0.x *= poseData[3]; + output[i].m.v1.x *= poseData[3]; + output[i].m.v2.x *= poseData[3]; - int p = bones[i].mParentIdx; + p = bones[i].mParentIdx; if (p >= 0) { MatrixMultiply(&output[i], &output[p], &output[i]); } @@ -55,218 +55,242 @@ void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask * } void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { - int numBones = GetNumBones(); + int n = GetNumBones(); + int i; if (mask) { if (GetInvBoneScales()) { - for (int i = 0; i < numBones; i++) { + for (i = 0; i < n; i++) { if (mask->GetBone(i)) { - const BoneData &bone = GetBoneData(i); + const BoneData &bd = GetBoneData(i); - pose[0] = bone.mS.x; - pose[1] = bone.mS.y; - pose[2] = bone.mS.z; + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + pose[2] = bd.mS.z; pose[3] = GetInvBoneScales()[i]; - pose[4] = bone.mQ.x; - pose[5] = bone.mQ.y; - pose[6] = bone.mQ.z; - pose[7] = bone.mQ.w; - pose[8] = bone.mT.x; - pose[9] = bone.mT.y; - pose[10] = bone.mT.z; + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; pose[11] = 1.0f; } pose += 12; } } else { - for (int i = 0; i < numBones; i++) { + for (i = 0; i < n; i++) { if (mask->GetBone(i)) { - const BoneData &bone = GetBoneData(i); + const BoneData &bd = GetBoneData(i); + const float one = 1.0f; + float z = bd.mS.z; - pose[0] = bone.mS.x; - pose[1] = bone.mS.y; - pose[3] = 1.0f; - pose[2] = bone.mS.z; - pose[4] = bone.mQ.x; - pose[5] = bone.mQ.y; - pose[6] = bone.mQ.z; - pose[7] = bone.mQ.w; - pose[8] = bone.mT.x; - pose[9] = bone.mT.y; - pose[10] = bone.mT.z; - pose[11] = 1.0f; + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + pose[2] = (pose[3] = one, z); + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; + pose[11] = one; } pose += 12; } } } else if (GetInvBoneScales()) { - for (int i = 0; i < numBones; i++) { - const BoneData &bone = GetBoneData(i); + for (i = 0; i < n; i++) { + const BoneData &bd = GetBoneData(i); - pose[0] = bone.mS.x; - pose[1] = bone.mS.y; - pose[2] = bone.mS.z; + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + pose[2] = bd.mS.z; pose[3] = GetInvBoneScales()[i]; - pose[4] = bone.mQ.x; - pose[5] = bone.mQ.y; - pose[6] = bone.mQ.z; - pose[7] = bone.mQ.w; - pose[8] = bone.mT.x; - pose[9] = bone.mT.y; - pose[10] = bone.mT.z; + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; pose[11] = 1.0f; pose += 12; } } else { - for (int i = 0; i < numBones; i++) { - const BoneData &bone = GetBoneData(i); + for (i = 0; i < n; i++) { + const BoneData &bd = GetBoneData(i); + const float one = 1.0f; + float z = bd.mS.z; - pose[0] = bone.mS.x; - pose[1] = bone.mS.y; - pose[3] = 1.0f; - pose[2] = bone.mS.z; - pose[4] = bone.mQ.x; - pose[5] = bone.mQ.y; - pose[6] = bone.mQ.z; - pose[7] = bone.mQ.w; - pose[8] = bone.mT.x; - pose[9] = bone.mT.y; - pose[10] = bone.mT.z; - pose[11] = 1.0f; + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + pose[2] = (pose[3] = one, z); + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; + pose[11] = one; pose += 12; } } } void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const BoneMask *mask) { - int numBones = GetNumBones(); + const int DOF = 12; + const int n = GetNumBones(); + int i; + float *ps; + float *pd; if (mask) { if (pose == mirrorPose) { - for (int ibone = 0; ibone < numBones; ibone++) { - if (mask->GetBone(ibone)) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; - - if (ibone < mirrorBone) { - float *dst = &mirrorPose[mirrorBone * 12]; - float *src = &mirrorPose[ibone * 12]; - float value = dst[4]; + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + int lrIdx = GetBoneData(i).mLeftRightIdx; - dst[4] = -src[4]; - src[4] = -value; - value = dst[5]; - dst[5] = -src[5]; - src[5] = -value; - value = dst[6]; - dst[6] = src[6]; - src[6] = value; - value = dst[7]; - dst[7] = src[7]; - src[7] = value; - value = dst[8]; - dst[8] = src[8]; - src[8] = value; - value = dst[9]; - dst[9] = src[9]; - src[9] = value; - value = dst[10]; - dst[10] = -src[10]; - src[10] = -value; - } else if (mirrorBone == ibone) { - float *dst = &mirrorPose[ibone * 12]; + if (lrIdx > i) { + float tmp; - dst[4] = -dst[4]; - dst[5] = -dst[5]; - dst[10] = -dst[10]; + pd = &mirrorPose[lrIdx * DOF]; + ps = &mirrorPose[i * DOF]; + tmp = pd[4]; + pd[4] = -ps[4]; + ps[4] = -tmp; + tmp = pd[5]; + pd[5] = -ps[5]; + ps[5] = -tmp; + tmp = pd[6]; + pd[6] = ps[6]; + ps[6] = tmp; + tmp = pd[7]; + pd[7] = ps[7]; + ps[7] = tmp; + tmp = pd[8]; + pd[8] = ps[8]; + ps[8] = tmp; + tmp = pd[9]; + pd[9] = ps[9]; + ps[9] = tmp; + tmp = pd[10]; + pd[10] = -ps[10]; + ps[10] = -tmp; + } else if (lrIdx == i) { + pd = &mirrorPose[i * DOF]; + pd[4] = -pd[4]; + pd[5] = -pd[5]; + pd[10] = -pd[10]; } } } } else { - for (int ibone = 0; ibone < numBones; ibone++) { - if (mask->GetBone(ibone)) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; - float *src = &pose[ibone * 12]; - float *dst = &mirrorPose[mirrorBone * 12]; + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + int lrIdx = GetBoneData(i).mLeftRightIdx; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[4] = -src[4]; - dst[5] = -src[5]; - dst[6] = src[6]; - dst[7] = src[7]; - dst[8] = src[8]; - dst[9] = src[9]; - dst[10] = -src[10]; + ps = &pose[i * DOF]; + pd = &mirrorPose[lrIdx * DOF]; + pd[0] = ps[0]; + pd[1] = ps[1]; + pd[2] = ps[2]; + pd[4] = -ps[4]; + pd[5] = -ps[5]; + pd[6] = ps[6]; + pd[7] = ps[7]; + pd[8] = ps[8]; + pd[9] = ps[9]; + pd[10] = -ps[10]; } } } - } else if (pose == mirrorPose) { - for (int ibone = 0; ibone < numBones; ibone++) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; + if (!local) { + pd = mirrorPose; + UMath::Vector4 quat = *reinterpret_cast(&pd[4]); + float v8 = pd[8]; + float v10 = pd[10]; + + pd[4] = quat.z; + pd[5] = quat.w; + pd[6] = -quat.x; + pd[10] = -v10; + pd[8] = -v8; + pd[7] = -quat.y; + } + } else { + if (pose == mirrorPose) { + for (i = 0; i < n; i++) { + int lrIdx = GetBoneData(i).mLeftRightIdx; - if (ibone < mirrorBone) { - float *dst = &mirrorPose[mirrorBone * 12]; - float *src = &mirrorPose[ibone * 12]; - float value = dst[4]; + if (lrIdx > i) { + float tmp; - dst[4] = -src[4]; - src[4] = -value; - value = dst[5]; - dst[5] = -src[5]; - src[5] = -value; - value = dst[6]; - dst[6] = src[6]; - src[6] = value; - value = dst[7]; - dst[7] = src[7]; - src[7] = value; - value = dst[8]; - dst[8] = src[8]; - src[8] = value; - value = dst[9]; - dst[9] = src[9]; - src[9] = value; - value = dst[10]; - dst[10] = -src[10]; - src[10] = -value; - } else if (mirrorBone == ibone) { - float *dst = &mirrorPose[ibone * 12]; + pd = &mirrorPose[lrIdx * DOF]; + ps = &mirrorPose[i * DOF]; + tmp = pd[4]; + pd[4] = -ps[4]; + ps[4] = -tmp; + tmp = pd[5]; + pd[5] = -ps[5]; + ps[5] = -tmp; + tmp = pd[6]; + pd[6] = ps[6]; + ps[6] = tmp; + tmp = pd[7]; + pd[7] = ps[7]; + ps[7] = tmp; + tmp = pd[8]; + pd[8] = ps[8]; + ps[8] = tmp; + tmp = pd[9]; + pd[9] = ps[9]; + ps[9] = tmp; + tmp = pd[10]; + pd[10] = -ps[10]; + ps[10] = -tmp; + } else if (lrIdx == i) { + pd = &mirrorPose[i * DOF]; + pd[4] = -pd[4]; + pd[5] = -pd[5]; + pd[10] = -pd[10]; + } + } + } else { + for (i = 0; i < n; i++) { + int lrIdx = GetBoneData(i).mLeftRightIdx; - dst[4] = -dst[4]; - dst[5] = -dst[5]; - dst[10] = -dst[10]; + ps = &pose[i * DOF]; + pd = &mirrorPose[lrIdx * DOF]; + pd[0] = ps[0]; + pd[1] = ps[1]; + pd[2] = ps[2]; + pd[4] = -ps[4]; + pd[5] = -ps[5]; + pd[6] = ps[6]; + pd[7] = ps[7]; + pd[8] = ps[8]; + pd[9] = ps[9]; + pd[10] = -ps[10]; } } - } else { - for (int ibone = 0; ibone < numBones; ibone++) { - int mirrorBone = GetBoneData(ibone).mLeftRightIdx; - float *src = &pose[ibone * 12]; - float *dst = &mirrorPose[mirrorBone * 12]; + if (!local) { + pd = mirrorPose; + UMath::Vector4 quat = *reinterpret_cast(&pd[4]); + float v8 = pd[8]; + float v10 = pd[10]; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[4] = -src[4]; - dst[5] = -src[5]; - dst[6] = src[6]; - dst[7] = src[7]; - dst[8] = src[8]; - dst[9] = src[9]; - dst[10] = -src[10]; + pd[4] = quat.z; + pd[5] = quat.w; + pd[6] = -quat.x; + pd[10] = -v10; + pd[8] = -v8; + pd[7] = -quat.y; } } - - if (!local) { - UMath::Vector4 quat = *reinterpret_cast(&mirrorPose[4]); - - mirrorPose[4] = quat.z; - mirrorPose[5] = quat.w; - mirrorPose[6] = -quat.x; - mirrorPose[10] = -mirrorPose[10]; - mirrorPose[8] = -mirrorPose[8]; - mirrorPose[7] = -quat.y; - } } }; // namespace EAGL4Anim From ecec7297a0d975c7eae8f2542dcca271b6208173 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 23:06:22 +0100 Subject: [PATCH 219/372] 81.1%: match QuatMultQxZ and tighten GetStillPose Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 28 ++--------------- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 30 +++++++++++++++++-- src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp | 18 +++++------ 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index a9cf0013b..ba0efc742 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -4,31 +4,9 @@ #include "AnimUtil.h" -void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { - float awby = a.w * b.y; - float axbw = a.x * b.w; - float awbw = a.w * b.w; - float naxby = -(a.x * b.y); - - result.x = axbw * c.w - awby * c.z; - result.y = axbw * c.z + awby * c.w; - result.z = naxby * c.w + awbw * c.z; - result.w = -naxby * c.z + awbw * c.w; -} - -void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { - result.x = a.w * b.x + a.x * b.w; - result.y = a.w * b.y + a.x * b.z; - result.z = -(a.x * b.y) + a.w * b.z; - result.w = -(a.x * b.x) + a.w * b.w; -} - -void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { - result.x = a.x * b.w - a.y * b.z; - result.y = a.x * b.z + a.y * b.w; - result.z = a.z * b.w + a.w * b.z; - result.w = -(a.z * b.z) + a.w * b.w; -} +static void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result); +static void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +static void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); namespace EAGL4Anim { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 77f06489f..6899f7ab9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -4,9 +4,33 @@ #include "Speed/Indep/Src/EAGL4Anim/AnimTypeId.h" #include "Speed/Indep/Src/EAGL4Anim/AnimUtil.h" -void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result); -void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); -void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +static void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { + UMath::Vector4 q; + + q.x = a.x * b.w; + q.y = a.w * b.y; + q.w = a.w * b.w; + q.z = -(a.x * b.y); + + result.x = q.x * c.w - q.y * c.z; + result.y = q.x * c.z + q.y * c.w; + result.z = q.z * c.w + q.w * c.z; + result.w = -q.z * c.z + q.w * c.w; +} + +static void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w + a.w * b.x; + result.y = a.x * b.z + a.w * b.y; + result.z = (-a.x) * b.z + a.w * b.y; + result.w = (-a.x) * b.x + a.w * b.w; +} + +static void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w - a.y * b.z; + result.y = a.x * b.z + a.y * b.w; + result.z = a.z * b.w + a.w * b.z; + result.w = (-a.z) * b.z + a.w * b.w; +} namespace EAGL4Anim { diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index 8377cb65c..6e9b4d0c7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -84,10 +84,11 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { if (mask->GetBone(i)) { const BoneData &bd = GetBoneData(i); const float one = 1.0f; - float z = bd.mS.z; + float z; pose[0] = bd.mS.x; pose[1] = bd.mS.y; + z = bd.mS.z; pose[2] = (pose[3] = one, z); pose[4] = bd.mQ.x; pose[5] = bd.mQ.y; @@ -123,10 +124,11 @@ void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { for (i = 0; i < n; i++) { const BoneData &bd = GetBoneData(i); const float one = 1.0f; - float z = bd.mS.z; + float z; pose[0] = bd.mS.x; pose[1] = bd.mS.y; + z = bd.mS.z; pose[2] = (pose[3] = one, z); pose[4] = bd.mQ.x; pose[5] = bd.mQ.y; @@ -211,14 +213,12 @@ void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const Bone if (!local) { pd = mirrorPose; UMath::Vector4 quat = *reinterpret_cast(&pd[4]); - float v8 = pd[8]; - float v10 = pd[10]; pd[4] = quat.z; pd[5] = quat.w; pd[6] = -quat.x; - pd[10] = -v10; - pd[8] = -v8; + pd[8] = -pd[8]; + pd[10] = -pd[10]; pd[7] = -quat.y; } } else { @@ -280,14 +280,12 @@ void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const Bone if (!local) { pd = mirrorPose; UMath::Vector4 quat = *reinterpret_cast(&pd[4]); - float v8 = pd[8]; - float v10 = pd[10]; pd[4] = quat.z; pd[5] = quat.w; pd[6] = -quat.x; - pd[10] = -v10; - pd[8] = -v8; + pd[8] = -pd[8]; + pd[10] = -pd[10]; pd[7] = -quat.y; } } From c1b9e716164feda61c2b0f228be095c941ee70d2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 23:19:32 +0100 Subject: [PATCH 220/372] 81.1%: match more shared math helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 4 ++-- src/Speed/Indep/Src/EAGL4Anim/system.cpp | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 6899f7ab9..71f178fe6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -10,7 +10,7 @@ static void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, cons q.x = a.x * b.w; q.y = a.w * b.y; q.w = a.w * b.w; - q.z = -(a.x * b.y); + q.z = (-a.x) * b.y; result.x = q.x * c.w - q.y * c.z; result.y = q.x * c.z + q.y * c.w; @@ -21,7 +21,7 @@ static void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, cons static void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { result.x = a.x * b.w + a.w * b.x; result.y = a.x * b.z + a.w * b.y; - result.z = (-a.x) * b.z + a.w * b.y; + result.z = (-a.x) * b.y + a.w * b.z; result.w = (-a.x) * b.x + a.w * b.w; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/system.cpp b/src/Speed/Indep/Src/EAGL4Anim/system.cpp index f5445b47d..9433f7fc3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/system.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/system.cpp @@ -4,10 +4,8 @@ #include "dolphin/mtx44_ext.h" static void MtxMult(EAGL4::Transform *result, const EAGL4::Transform *A, const EAGL4::Transform *B) { - EAGL4::Transform temp; - const Mtx44 &src = *reinterpret_cast(&temp); - Mtx44 &dst = *reinterpret_cast(result); + EAGL4::Transform newMtx; - bMulMatrix(reinterpret_cast(&temp), reinterpret_cast(A), reinterpret_cast(B)); - PSMTX44Copy(src, dst); + bMulMatrix(reinterpret_cast(&newMtx), reinterpret_cast(A), reinterpret_cast(B)); + bCopy(reinterpret_cast(result), reinterpret_cast(&newMtx)); } From b32f8f18d3d61e57a17b1f3a68942e6037d75ece Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 23:47:49 +0100 Subject: [PATCH 221/372] 81.2%: improve FnStatelessQ EvalSQT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 407abd2c0..698e779f4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -80,29 +80,35 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) scale = currTime - floorTime; } } - } else if (floorTime < statelessQ->mTimes[0]) { - floorKey = 0; - slerpReqd = currTime != 0.0f; - if (slerpReqd) { - scale = currTime / static_cast(statelessQ->mTimes[0]); - } } else { - int timeIndex = 0; + if (floorTime < statelessQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex = 0; - if (mPrevKey != 0) { - timeIndex = mPrevKey - 1; - } - if (statelessQ->mTimes[timeIndex] > floorTime) { - while (timeIndex > 0 && statelessQ->mTimes[timeIndex] > floorTime) { - timeIndex--; + if (mPrevKey != 0) { + timeIndex = mPrevKey - 1; } - } else { - while (timeIndex < statelessQ->mNumKeys - 2 && statelessQ->mTimes[timeIndex + 1] <= floorTime) { - timeIndex++; + if (floorTime < statelessQ->mTimes[timeIndex]) { + if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { + break; + } + } while (floorTime < statelessQ->mTimes[timeIndex]); + } + } else if (timeIndex < statelessQ->mNumKeys - 2) { + while (statelessQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + if (timeIndex >= statelessQ->mNumKeys - 2) { + break; + } + } } - } - floorKey = timeIndex + 1; + floorKey = timeIndex + 1; + } if (floorKey == 0) { slerpReqd = currTime != 0.0f; if (slerpReqd) { @@ -129,59 +135,59 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mBoneMask = nullptr; } + float *q = sqt + 4; unsigned short *dataBuf = statelessQ->GetData(); unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; + int index; if (slerpReqd) { - unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); + int nextKey = floorKey + 1; + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); for (int ibone = 0; ibone < nBones; ibone++) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - LoadStatelessQ(frameData, prevQ); - LoadStatelessQ(nextFrameData, nextQ); - - float x = prevQ.x + (nextQ.x - prevQ.x) * scale; - float y = prevQ.y + (nextQ.y - prevQ.y) * scale; - float z = prevQ.z + (nextQ.z - prevQ.z) * scale; - float w = prevQ.w + (nextQ.w - prevQ.w) * scale; - float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); - - q[0] = x; - q[1] = y; - q[2] = z; - q[3] = w; - - frameData += 4; - nextFrameData += 4; + prevQ.x = UncompressStatelessQValue(*frameData++); + prevQ.y = UncompressStatelessQValue(*frameData++); + prevQ.z = UncompressStatelessQValue(*frameData++); + prevQ.w = UncompressStatelessQValue(*frameData++); + nextQ.x = UncompressStatelessQValue(*nextFrameData++); + nextQ.y = UncompressStatelessQValue(*nextFrameData++); + nextQ.z = UncompressStatelessQValue(*nextFrameData++); + nextQ.w = UncompressStatelessQValue(*nextFrameData++); + index = boneIdxs[ibone] * 12; + + q[index + 0] = prevQ.x + (nextQ.x - prevQ.x) * scale; + q[index + 1] = prevQ.y + (nextQ.y - prevQ.y) * scale; + q[index + 2] = prevQ.z + (nextQ.z - prevQ.z) * scale; + q[index + 3] = prevQ.w + (nextQ.w - prevQ.w) * scale; } } else { for (int ibone = 0; ibone < nBones; ibone++) { - float *q = GetStatelessQOutput(sqt, boneIdxs[ibone]); + index = boneIdxs[ibone] * 12; - q[0] = UncompressStatelessQValue(frameData[0]); - q[1] = UncompressStatelessQValue(frameData[1]); - q[2] = UncompressStatelessQValue(frameData[2]); - q[3] = UncompressStatelessQValue(frameData[3]); - frameData += 4; + q[index + 0] = UncompressStatelessQValue(*frameData++); + q[index + 1] = UncompressStatelessQValue(*frameData++); + q[index + 2] = UncompressStatelessQValue(*frameData++); + q[index + 3] = UncompressStatelessQValue(*frameData++); } } if (statelessQ->mNumConstBones != 0) { + int numConsts = statelessQ->mNumConstBones; unsigned short *constBuf = statelessQ->GetConstData(dataBuf); unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); - for (int ibone = 0; ibone < statelessQ->mNumConstBones; ibone++) { - float *q = GetStatelessQOutput(sqt, *constIdxs++); + for (int ibone = 0; ibone < numConsts; ibone++) { + index = *constIdxs++ * 12; - q[0] = UncompressStatelessQValue(constBuf[0]); - q[1] = UncompressStatelessQValue(constBuf[1]); - q[2] = UncompressStatelessQValue(constBuf[2]); - q[3] = UncompressStatelessQValue(constBuf[3]); - constBuf += 4; + q[index + 0] = UncompressStatelessQValue(*constBuf++); + q[index + 1] = UncompressStatelessQValue(*constBuf++); + q[index + 2] = UncompressStatelessQValue(*constBuf++); + q[index + 3] = UncompressStatelessQValue(*constBuf++); } } } else { From 3f9e8e924426de54ef0192490808d92180067b2f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 23:49:59 +0100 Subject: [PATCH 222/372] 81.3%: refine FnStatelessQ EvalSQT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 698e779f4..fdf8ddccf 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -73,12 +73,10 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) floorKey = floorTime; } - slerpReqd = floorKey < statelessQ->mNumKeys - 1; - if (slerpReqd) { - slerpReqd = currTime != floorTime; - if (slerpReqd) { - scale = currTime - floorTime; - } + slerpReqd = currTime != floorTime; + scale = currTime - floorTime; + if (floorKey >= statelessQ->mNumKeys - 1) { + slerpReqd = false; } } else { if (floorTime < statelessQ->mTimes[0]) { @@ -89,22 +87,22 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) if (mPrevKey != 0) { timeIndex = mPrevKey - 1; } - if (floorTime < statelessQ->mTimes[timeIndex]) { - if (timeIndex > 0) { - do { - timeIndex--; - if (timeIndex < 1) { + if (statelessQ->mTimes[timeIndex] <= floorTime) { + if (timeIndex < statelessQ->mNumKeys - 2) { + while (statelessQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + if (timeIndex >= statelessQ->mNumKeys - 2) { break; } - } while (floorTime < statelessQ->mTimes[timeIndex]); + } } - } else if (timeIndex < statelessQ->mNumKeys - 2) { - while (statelessQ->mTimes[timeIndex + 1] <= floorTime) { - timeIndex++; - if (timeIndex >= statelessQ->mNumKeys - 2) { + } else if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { break; } - } + } while (statelessQ->mTimes[timeIndex] > floorTime); } floorKey = timeIndex + 1; @@ -143,8 +141,7 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) int index; if (slerpReqd) { - int nextKey = floorKey + 1; - unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { UMath::Vector4 prevQ; From da13864213bb0a58d6143ea4845411b0fc10dcfa Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Mon, 23 Mar 2026 23:53:36 +0100 Subject: [PATCH 223/372] 81.4%: refine stateless evaluator timing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 52 +++++++++-------- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 57 +++++++++++-------- 2 files changed, 61 insertions(+), 48 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 60281414b..d6c0b79da 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -60,36 +60,40 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask floorKey = floorTime; } - slerpReqd = floorKey < statelessF3->mNumKeys - 1; - if (slerpReqd) { - slerpReqd = currTime != floorTime; - if (slerpReqd) { - scale = currTime - floorTime; - } - } - } else if (floorTime < statelessF3->mTimes[0]) { - floorKey = 0; - slerpReqd = currTime != 0.0f; - if (slerpReqd) { - scale = currTime / static_cast(statelessF3->mTimes[0]); + slerpReqd = currTime != floorTime; + scale = currTime - floorTime; + if (floorKey >= statelessF3->mNumKeys - 1) { + slerpReqd = false; } } else { - int timeIndex = 0; + if (floorTime < statelessF3->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex = 0; - if (mPrevKey != 0) { - timeIndex = mPrevKey - 1; - } - if (floorTime < statelessF3->mTimes[timeIndex]) { - while (timeIndex > 0 && floorTime < statelessF3->mTimes[timeIndex]) { - timeIndex--; + if (mPrevKey != 0) { + timeIndex = mPrevKey - 1; } - } else { - while (timeIndex < statelessF3->mNumKeys - 2 && floorTime >= statelessF3->mTimes[timeIndex + 1]) { - timeIndex++; + if (statelessF3->mTimes[timeIndex] <= floorTime) { + if (timeIndex < statelessF3->mNumKeys - 2) { + while (statelessF3->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + if (timeIndex >= statelessF3->mNumKeys - 2) { + break; + } + } + } + } else if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { + break; + } + } while (statelessF3->mTimes[timeIndex] > floorTime); } - } - floorKey = timeIndex + 1; + floorKey = timeIndex + 1; + } if (floorKey == 0) { slerpReqd = currTime != 0.0f; if (slerpReqd) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index fdf8ddccf..59b2eacc4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -209,6 +209,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; + int index; if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); @@ -219,51 +220,59 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool if (boneMask->GetBone(boneIdx)) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - float *q = &sqt[boneIdx * 12]; - - LoadStatelessQ(frameData, prevQ); - LoadStatelessQ(nextFrameData, nextQ); - - q[0] = prevQ.x + (nextQ.x - prevQ.x) * scale; - q[1] = prevQ.y + (nextQ.y - prevQ.y) * scale; - q[2] = prevQ.z + (nextQ.z - prevQ.z) * scale; - q[3] = prevQ.w + (nextQ.w - prevQ.w) * scale; + int frameIndex = ibone * 4; + + prevQ.x = UncompressStatelessQValue(frameData[frameIndex + 0]); + prevQ.y = UncompressStatelessQValue(frameData[frameIndex + 1]); + prevQ.z = UncompressStatelessQValue(frameData[frameIndex + 2]); + prevQ.w = UncompressStatelessQValue(frameData[frameIndex + 3]); + nextQ.x = UncompressStatelessQValue(nextFrameData[frameIndex + 0]); + nextQ.y = UncompressStatelessQValue(nextFrameData[frameIndex + 1]); + nextQ.z = UncompressStatelessQValue(nextFrameData[frameIndex + 2]); + nextQ.w = UncompressStatelessQValue(nextFrameData[frameIndex + 3]); + index = boneIdx * 12; + + sqt[index + 0] = prevQ.x + (nextQ.x - prevQ.x) * scale; + sqt[index + 1] = prevQ.y + (nextQ.y - prevQ.y) * scale; + sqt[index + 2] = prevQ.z + (nextQ.z - prevQ.z) * scale; + sqt[index + 3] = prevQ.w + (nextQ.w - prevQ.w) * scale; } - frameData += 4; - nextFrameData += 4; } } else { for (int ibone = 0; ibone < nBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - float *q = &sqt[boneIdx * 12]; + int frameIndex = ibone * 4; - q[0] = UncompressStatelessQValue(frameData[0]); - q[1] = UncompressStatelessQValue(frameData[1]); - q[2] = UncompressStatelessQValue(frameData[2]); - q[3] = UncompressStatelessQValue(frameData[3]); + index = boneIdx * 12; + + sqt[index + 0] = UncompressStatelessQValue(frameData[frameIndex + 0]); + sqt[index + 1] = UncompressStatelessQValue(frameData[frameIndex + 1]); + sqt[index + 2] = UncompressStatelessQValue(frameData[frameIndex + 2]); + sqt[index + 3] = UncompressStatelessQValue(frameData[frameIndex + 3]); } - frameData += 4; } } if (statelessQ->mNumConstBones != 0) { + int numConsts = statelessQ->mNumConstBones; unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); unsigned short *constBuf = statelessQ->GetConstData(dataBuf); - for (int ibone = 0; ibone < statelessQ->mNumConstBones; ibone++) { + for (int ibone = 0; ibone < numConsts; ibone++) { unsigned char boneIdx = constIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - float *q = &sqt[boneIdx * 12]; + int frameIndex = ibone * 4; + + index = boneIdx * 12; - q[0] = UncompressStatelessQValue(constBuf[0]); - q[1] = UncompressStatelessQValue(constBuf[1]); - q[2] = UncompressStatelessQValue(constBuf[2]); - q[3] = UncompressStatelessQValue(constBuf[3]); + sqt[index + 0] = UncompressStatelessQValue(constBuf[frameIndex + 0]); + sqt[index + 1] = UncompressStatelessQValue(constBuf[frameIndex + 1]); + sqt[index + 2] = UncompressStatelessQValue(constBuf[frameIndex + 2]); + sqt[index + 3] = UncompressStatelessQValue(constBuf[frameIndex + 3]); } - constBuf += 4; } } From 91f1a05f835c0fb90002f4e6b63b38ef258c6d7a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:13:30 +0100 Subject: [PATCH 224/372] 81.5%: refine stateless evaluator interpolation --- .../Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 36 +++++++++---------- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 22 ++++++------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index d6c0b79da..1f535a11b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -136,18 +136,18 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); - int index = dofIdxs[ibone]; + unsigned short index = dofIdxs[ibone]; - sqt[index + 0] = prev.x + (next.x - prev.x) * scale; - sqt[index + 1] = prev.y + (next.y - prev.y) * scale; - sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + sqt[index + 0] = scale * (next.x - prev.x) + prev.x; + sqt[index + 1] = scale * (next.y - prev.y) + prev.y; + sqt[index + 2] = scale * (next.z - prev.z) + prev.z; frameData += 3; nextFrameData += 3; } } else { for (int ibone = 0; ibone < nBones; ibone++) { - int index = dofIdxs[ibone]; + unsigned short index = dofIdxs[ibone]; sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; @@ -162,7 +162,7 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { float *constBone = &constBuf[ibone * 3]; - int index = constIdxs[ibone]; + unsigned short index = constIdxs[ibone]; sqt[index + 0] = constBone[0]; sqt[index + 1] = constBone[1]; @@ -204,11 +204,11 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); - int index = dofIdxs[ibone]; + unsigned short index = dofIdxs[ibone]; - sqt[index + 0] = prev.x + (next.x - prev.x) * scale; - sqt[index + 1] = prev.y + (next.y - prev.y) * scale; - sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + sqt[index + 0] = scale * (next.x - prev.x) + prev.x; + sqt[index + 1] = scale * (next.y - prev.y) + prev.y; + sqt[index + 2] = scale * (next.z - prev.z) + prev.z; } frameData += 3; nextFrameData += 3; @@ -217,7 +217,7 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo for (int ibone = 0; ibone < nBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector3 value; - int index = dofIdxs[ibone]; + unsigned short index = dofIdxs[ibone]; UnquantizeStatelessF3(dofInfos[ibone], frameData, value); sqt[index + 0] = value.x; @@ -236,7 +236,7 @@ bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, boo unsigned char boneIdx = GetStatelessF3BoneIndex(constIdxs[ibone]); if (boneMask->GetBone(boneIdx)) { - int index = constIdxs[ibone]; + unsigned short index = constIdxs[ibone]; sqt[index + 0] = constBuf[0]; sqt[index + 1] = constBuf[1]; @@ -270,18 +270,18 @@ bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *bone UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); - int index = dofIdxs[ibone]; + unsigned short index = dofIdxs[ibone]; - sqt[index + 0] = prev.x + (next.x - prev.x) * scale; - sqt[index + 1] = prev.y + (next.y - prev.y) * scale; - sqt[index + 2] = prev.z + (next.z - prev.z) * scale; + sqt[index + 0] = scale * (next.x - prev.x) + prev.x; + sqt[index + 1] = scale * (next.y - prev.y) + prev.y; + sqt[index + 2] = scale * (next.z - prev.z) + prev.z; frameData += 3; nextFrameData += 3; } } else { for (int ibone = 0; ibone < nBones; ibone++) { - int index = dofIdxs[ibone]; + unsigned short index = dofIdxs[ibone]; sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; @@ -296,7 +296,7 @@ bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *bone for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { float *constBone = &constBuf[ibone * 3]; - int index = constIdxs[ibone]; + unsigned short index = constIdxs[ibone]; sqt[index + 0] = constBone[0]; sqt[index + 1] = constBone[1]; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 59b2eacc4..f824deea7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -138,9 +138,9 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; - int index; + unsigned short index; - if (slerpReqd) { + if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); for (int ibone = 0; ibone < nBones; ibone++) { @@ -157,10 +157,10 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) nextQ.w = UncompressStatelessQValue(*nextFrameData++); index = boneIdxs[ibone] * 12; - q[index + 0] = prevQ.x + (nextQ.x - prevQ.x) * scale; - q[index + 1] = prevQ.y + (nextQ.y - prevQ.y) * scale; - q[index + 2] = prevQ.z + (nextQ.z - prevQ.z) * scale; - q[index + 3] = prevQ.w + (nextQ.w - prevQ.w) * scale; + q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; + q[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; + q[index + 2] = scale * (nextQ.z - prevQ.z) + prevQ.z; + q[index + 3] = scale * (nextQ.w - prevQ.w) + prevQ.w; } } else { for (int ibone = 0; ibone < nBones; ibone++) { @@ -209,7 +209,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; - int index; + unsigned short index; if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); @@ -232,10 +232,10 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool nextQ.w = UncompressStatelessQValue(nextFrameData[frameIndex + 3]); index = boneIdx * 12; - sqt[index + 0] = prevQ.x + (nextQ.x - prevQ.x) * scale; - sqt[index + 1] = prevQ.y + (nextQ.y - prevQ.y) * scale; - sqt[index + 2] = prevQ.z + (nextQ.z - prevQ.z) * scale; - sqt[index + 3] = prevQ.w + (nextQ.w - prevQ.w) * scale; + sqt[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; + sqt[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; + sqt[index + 2] = scale * (nextQ.z - prevQ.z) + prevQ.z; + sqt[index + 3] = scale * (nextQ.w - prevQ.w) + prevQ.w; } } } else { From ae2c93b3153613af72084039f2b06012df8f01d4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:15:51 +0100 Subject: [PATCH 225/372] 81.6%: match stateless init helpers --- src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp | 7 ++++--- src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp | 13 ++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp index 0369d696e..bc482e6c9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp @@ -5,11 +5,12 @@ namespace EAGL4Anim { void StatelessF3::InitAnimMemoryMap(AnimMemoryMap *anim) { StatelessF3 *statelessF3 = reinterpret_cast(anim); - FnStatelessF3 fnStatelessF3; FnStatelessF3 *fnStatelessF3Ptr = reinterpret_cast(statelessF3->GetFnLocation()); - *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); - fnStatelessF3Ptr->SetAnimMemoryMap(statelessF3); + { + FnStatelessF3 fnStatelessF3; + *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + } } }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp index 4917d606a..c64f1f05f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp @@ -7,18 +7,21 @@ namespace EAGL4Anim { void StatelessQ::InitAnimMemoryMap(AnimMemoryMap *anim) { StatelessQ *statelessQ = reinterpret_cast(anim); - FnStatelessQ fnStatelessQ; FnStatelessQ *fnStatelessQPtr = reinterpret_cast(statelessQ->GetFnLocation()); - *reinterpret_cast(fnStatelessQPtr) = *reinterpret_cast(&fnStatelessQ); - fnStatelessQPtr->SetAnimMemoryMap(anim); + { + FnStatelessQ fnStatelessQ; + *reinterpret_cast(fnStatelessQPtr) = *reinterpret_cast(&fnStatelessQ); + } if (statelessQ->mF3Ptr) { StatelessF3 *statelessF3 = reinterpret_cast(statelessQ->mF3Ptr); - FnStatelessF3 fnStatelessF3; FnStatelessF3 *fnStatelessF3Ptr = reinterpret_cast(statelessF3->GetFnLocation()); - *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + { + FnStatelessF3 fnStatelessF3; + *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + } fnStatelessF3Ptr->SetAnimMemoryMap(statelessF3); statelessQ->mF3Ptr = fnStatelessF3Ptr; } From 319f9bee1cef8feddf7c037e31c899a6a8206746 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:25:41 +0100 Subject: [PATCH 226/372] 81.63%: improve RawPoseChannel init signatures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index 0cf219e46..e696e0407 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -1,5 +1,7 @@ #include "RawPoseChannel.h" +#include + namespace EAGL4Anim { void QuatF4(float *&data, float *output); @@ -14,40 +16,50 @@ void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { int numSigs = rawPoseChannel->mSigSize; int *sig = rawPoseChannel->GetNonInterpSig(); - for (int isig = 0; isig < numSigs; isig++) { + int isig = 0; + while (isig < numSigs) { int numChannels = *sig++; + isig++; for (int ichan = 0; ichan < numChannels; ichan++) { switch (*sig) { - case QUAT: - *sig = reinterpret_cast(QuatF4); - break; case EUL: *sig = reinterpret_cast(EulF3); break; + case QUAT: + *sig = reinterpret_cast(QuatF4); + break; case TRAN: *sig = reinterpret_cast(TranF3); break; + default: + printf("Bad signature channel type\n"); + break; } sig++; } } sig = rawPoseChannel->GetInterpSig(); - for (int isig = 0; isig < numSigs; isig++) { + isig = 0; + while (isig < numSigs) { int numChannels = *sig++; + isig++; for (int ichan = 0; ichan < numChannels; ichan++) { switch (*sig) { - case QUAT: - *sig = reinterpret_cast(QuatF4Interp); - break; case EUL: *sig = reinterpret_cast(EulF3Interp); break; + case QUAT: + *sig = reinterpret_cast(QuatF4Interp); + break; case TRAN: *sig = reinterpret_cast(TranF3Interp); break; + default: + printf("Bad signature channel type\n"); + break; } sig++; } From 8b301358a0635b858130db600100e6ea7bfd909a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:32:51 +0100 Subject: [PATCH 227/372] 82.71%: match FnCycle inline wrappers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnCycle.h | 35 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h index 9db6fe7f1..ad32e6b8e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h @@ -6,6 +6,7 @@ #pragma once #endif +#include "AnimUtil.h" #include "FnAnim.h" namespace EAGL4Anim { @@ -57,6 +58,27 @@ class FnCycle : public FnAnim { return mEndTime; } + private: + float GetInRangeTime(float t) const { + float tmp; + int n; + + if (t < mStartTime) { + tmp = t - mStartTime; + n = FloatToInt(tmp / mLength); + return mEndTime - (tmp - static_cast(n) * mLength); + } + + if (t <= mEndTime) { + return t; + } + + tmp = t - mEndTime; + n = FloatToInt(tmp / mLength); + return mStartTime + (tmp - static_cast(n) * mLength); + } + + public: // Overrides: FnAnim void Eval(float previousTime, float currentTime, float *dofs) override { mpAnim->Eval(GetInRangeTime(previousTime), GetInRangeTime(currentTime), dofs); @@ -77,19 +99,6 @@ class FnCycle : public FnAnim { return mpAnim->EvalPhase(GetInRangeTime(currentTime), phase); } - private: - float GetInRangeTime(float t) const { - if (t < mStartTime) { - return mEndTime - ((t - mStartTime) - static_cast(static_cast((t - mStartTime) / mLength)) * mLength); - } else { - if (t <= mEndTime) { - return t; - } - t = t - mEndTime; - return mStartTime + (t - static_cast(static_cast(t / mLength)) * mLength); - } - } - float mStartTime; // offset 0xC, size 0x4 float mEndTime; // offset 0x10, size 0x4 float mLength; // offset 0x14, size 0x4 From cc1b0f52207de406fe8225f631dd9bcedc198d7e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:40:59 +0100 Subject: [PATCH 228/372] 82.78%: inline qfast delta accumulators Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 51 +++---------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index ba0efc742..28efaa9ca 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -229,7 +229,8 @@ void FnDeltaQFast::InitBuffers() { } } -void FnDeltaQFast::AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) { +inline void FnDeltaQFast::AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, + UMath::Vector4 *prevQs) { unsigned char *binData = reinterpret_cast(floorPhys); for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { @@ -247,7 +248,8 @@ void FnDeltaQFast::AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, i } } -void FnDeltaQFast::SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) { +inline void FnDeltaQFast::SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, + UMath::Vector4 *prevQs) { unsigned char *binData = reinterpret_cast(floorPhys); for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { @@ -522,50 +524,9 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } if (prevDeltaIdx < static_cast(floorDeltaIdx)) { - unsigned char *deltaData = - reinterpret_cast(binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3); - - do { - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - prevQ[0] = prevQ[0] + minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]; - prevQ[1] = prevQ[1] + minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]; - prevQ[2] = prevQ[2] + minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]; - prevQ[3] = prevQ[3] + - minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * - kQFastDeltaScale6 + - minRange[3]; - deltaData += 3; - } - prevDeltaIdx++; - } while (prevDeltaIdx < static_cast(floorDeltaIdx)); + AddDelta(reinterpret_cast(binData), deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); } else if (static_cast(floorDeltaIdx) < prevDeltaIdx) { - unsigned char *deltaData = reinterpret_cast( - binData + deltaQ->mNumBones * 6 + prevDeltaIdx * deltaQ->mNumBones * 3 - 3); - - while ((prevDeltaIdx--), static_cast(floorDeltaIdx) <= prevDeltaIdx) { - for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - prevQ[0] = prevQ[0] - (minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0]); - prevQ[1] = prevQ[1] - (minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1]); - prevQ[2] = prevQ[2] - (minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2]); - prevQ[3] = prevQ[3] - - (minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * - kQFastDeltaScale6 + - minRange[3]); - deltaData -= 3; - } - } + SubDelta(reinterpret_cast(binData), deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); } } From 8d7325c16605d7267be1453822e3d43d9d14c603 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:45:24 +0100 Subject: [PATCH 229/372] 82.84%: use qfast unquantize helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h | 18 ++++++++- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 37 ++++++------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h index 042369d62..a2dfb7f93 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h @@ -27,7 +27,14 @@ struct DeltaQFastMinRange { // total size: 0x6 struct DeltaQFastPhysical { - void UnQuantize(UMath::Vector4 &q) const {} + void UnQuantize(UMath::Vector4 &q) const { + unsigned short w = static_cast((mW0 << 8) | (mW1 << 4) | mW2); + + q.x = static_cast(mX) * 4.8840049e-4f - 1.0002443f; + q.y = static_cast(mY) * 4.8840049e-4f - 1.0002443f; + q.z = static_cast(mZ) * 4.8840049e-4f - 1.0002443f; + q.w = static_cast(w) * 4.8840049e-4f - 1.0002443f; + } unsigned short mX : 12; // offset 0x0, size 0x2 unsigned short mW0 : 4; // offset 0x0, size 0x2 @@ -39,7 +46,14 @@ struct DeltaQFastPhysical { // total size: 0x3 struct DeltaQFastDelta { - void UnQuantize(const DeltaQFastMinRangef &minRangef, UMath::Vector4 &q) const {} + void UnQuantize(const DeltaQFastMinRangef &minRangef, UMath::Vector4 &q) const { + unsigned char w = static_cast((mW0 << 4) | (mW1 << 2) | mW2); + + q.x = minRangef.mMin.x + minRangef.mRange.x * static_cast(mX) * 1.5873017e-2f; + q.y = minRangef.mMin.y + minRangef.mRange.y * static_cast(mY) * 1.5873017e-2f; + q.z = minRangef.mMin.z + minRangef.mRange.z * static_cast(mZ) * 1.5873017e-2f; + q.w = minRangef.mMin.w + minRangef.mRange.w * static_cast(w) * 1.5873017e-2f; + } unsigned char mX : 6; // offset 0x0, size 0x1 unsigned char mW0 : 2; // offset 0x0, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 28efaa9ca..b5dd5625a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -277,38 +277,25 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx if (ceilBinIdx != floorBinIdx) { unsigned char numBones = deltaQ->mNumBones; - DeltaQFastPhysical *physical = reinterpret_cast(binData); + DeltaQFastPhysical *ceilPhys = reinterpret_cast(binData); for (int ibone = 0; ibone < numBones; ibone++) { - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - - nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - nextQ[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; - physical++; + ceilPhys[ibone].UnQuantize(mNextQs[ibone]); } } else { unsigned int numBones = deltaQ->mNumBones; - unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; + int ceilDeltaIdx = floorDeltaIdx; + DeltaQFastDelta *ceilDelta = reinterpret_cast(&binData[numBones * 6 + ceilDeltaIdx * numBones * 3]); for (int ibone = 0; ibone < static_cast(numBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - nextQ[0] = minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0] + prevQ[0]; - nextQ[1] = minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1] + prevQ[1]; - nextQ[2] = minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2] + prevQ[2]; - nextQ[3] = - minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + - minRange[3] + prevQ[3]; - deltaData += 3; + UMath::Vector4 ceilq; + + ceilDelta->UnQuantize(mMinRangesf[ibone], ceilq); + mNextQs[ibone].x = ceilq.x + mPrevQs[ibone].x; + mNextQs[ibone].y = ceilq.y + mPrevQs[ibone].y; + mNextQs[ibone].z = ceilq.z + mPrevQs[ibone].z; + mNextQs[ibone].w = ceilq.w + mPrevQs[ibone].w; + ceilDelta++; } } From 10d127c18a81213ec5294b2182f3cd74ad08748f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 00:49:35 +0100 Subject: [PATCH 230/372] 83.60%: inline singleq owner helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h | 68 ++++++++++++++++--- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 42 +++++++----- 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h index 0a3b3f91c..1f7d495e5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h @@ -22,9 +22,21 @@ struct DeltaSingleQMinRangef { // total size: 0xE struct DeltaSingleQMinRange { - void GetAlignment(float &c0, float &c1) {} + void GetAlignment(float &c0, float &c1) { + c0 = mConst0 * 9.5875265e-5f - 3.1415927f; + c1 = mConst1 * 9.5875265e-5f - 3.1415927f; + } + + void UnQuantize(DeltaSingleQMinRangef &minRangef) { + const float RangeScale16Bit = 3.0518044e-5f; - void UnQuantize(DeltaSingleQMinRangef &minRangef) {} + minRangef.mMin[0] = mMin[0] * RangeScale16Bit - 1.0f; + minRangef.mMin[1] = mMin[1] * RangeScale16Bit - 1.0f; + minRangef.mRange[0] = mRange[0] * 8.1381455e-6f; + minRangef.mRange[1] = mRange[1] * 8.1381455e-6f; + minRangef.mIndex = static_cast(mIndex); + GetAlignment(minRangef.mConst0, minRangef.mConst1); + } unsigned short mConst0; // offset 0x0, size 0x2 unsigned short mConst1; // offset 0x2, size 0x2 @@ -35,7 +47,22 @@ struct DeltaSingleQMinRange { // total size: 0x2 struct DeltaSingleQPhysical { - void UnQuantize(int index, UMath::Vector4 &q) const {} + void UnQuantize(int index, UMath::Vector4 &q) const { + const float RangeScale8Bit = 7.8431377e-3f; + + q.z = 0.0f; + q.y = 0.0f; + q.x = 0.0f; + + if (index == 0) { + q.x = mV * RangeScale8Bit - 1.0f; + } else if (index == 1) { + q.y = mV * RangeScale8Bit - 1.0f; + } else { + q.z = mV * RangeScale8Bit - 1.0f; + } + q.w = mW * RangeScale8Bit - 1.0f; + } unsigned char mV; // offset 0x0, size 0x1 unsigned char mW; // offset 0x1, size 0x1 @@ -43,7 +70,20 @@ struct DeltaSingleQPhysical { // total size: 0x1 struct DeltaSingleQDelta { - void UnQuantize(const DeltaSingleQMinRangef &minRangef, UMath::Vector4 &q) {} + void UnQuantize(const DeltaSingleQMinRangef &minRangef, UMath::Vector4 &q) { + q.z = 0.0f; + q.y = 0.0f; + q.x = 0.0f; + + if (minRangef.mIndex == 0) { + q.x = minRangef.mRange[0] * mV + minRangef.mMin[0]; + } else if (minRangef.mIndex == 1) { + q.y = minRangef.mRange[0] * mV + minRangef.mMin[0]; + } else { + q.z = minRangef.mRange[0] * mV + minRangef.mMin[0]; + } + q.w = minRangef.mRange[1] * mW + minRangef.mMin[1]; + } unsigned char mV : 4; // offset 0x0, size 0x1 unsigned char mW : 4; // offset 0x0, size 0x1 @@ -78,11 +118,14 @@ struct DeltaSingleQ : public AnimMemoryMap { return result; } - void GetArrays(DeltaSingleQMinRange *&minRanges, unsigned char *&binStart) {} + void GetArrays(DeltaSingleQMinRange *&minRanges, unsigned char *&binStart) { + minRanges = GetMinRange(); + binStart = &reinterpret_cast(minRanges)[mNumBones * sizeof(DeltaSingleQMinRange)]; + } int GetBinSize() const { - // TODO - // return AlignSize2(3 * ((GetBinLength() - 1) * mNumBones)); + return static_cast(AlignSize2((mNumBones * sizeof(DeltaSingleQPhysical)) + + ((GetBinLength() - 1) * mNumBones * sizeof(DeltaSingleQDelta)))); } DeltaSingleQMinRange *GetMinRange() { @@ -91,16 +134,19 @@ struct DeltaSingleQ : public AnimMemoryMap { } unsigned char *GetBin(int binIdx) { - // const int bs = GetBinSize(); - // unsigned char *memPos = &reinterpret_cast(GetMinRange())[mNumBones * sizeof(DeltaSingleQMinRange)]; - // return &memPos[binIdx * bs]; + const int bs = GetBinSize(); + unsigned char *memPos = &reinterpret_cast(GetMinRange())[mNumBones * sizeof(DeltaSingleQMinRange)]; + return &memPos[binIdx * bs]; } DeltaSingleQPhysical *GetPhysical(unsigned char *binData) { return reinterpret_cast(binData); } - DeltaSingleQDelta *GetDelta(unsigned char *binData, int deltaIdx) {} + DeltaSingleQDelta *GetDelta(unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[mNumBones * sizeof(DeltaSingleQPhysical) + + (deltaIdx * mNumBones * sizeof(DeltaSingleQDelta))]); + } unsigned short *GetConstBoneIdx() { unsigned int binLen = GetBinLength(); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 71f178fe6..fcc33cdce 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -224,11 +224,10 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas if (!mPrevQs) { unsigned char numBones = deltaQ->mNumBones; - DeltaSingleQMinRange *minRanges = - reinterpret_cast(reinterpret_cast(deltaQ) + 0x10); + DeltaSingleQMinRange *minRanges; - mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaSingleQMinRange); - mBinSize = AlignSize2(numBones * ((1 << deltaQ->mBinLengthPower) + 1)); + deltaQ->GetArrays(minRanges, mBins); + mBinSize = deltaQ->GetBinSize(); if (deltaQ->mNumBones != 0) { float eul[3]; @@ -244,7 +243,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { DeltaSingleQMinRangef minRangef; - DecodeSingleQMinRange(mMinRanges[ibone], minRangef); + mMinRanges[ibone].UnQuantize(minRangef); if (minRangef.mIndex == 0) { mPreMultQs[ibone].x = kSingleQFloatZero; @@ -327,7 +326,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorKey < mPrevKey) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DecodeSingleQPhysical(floorPhys[ibone], mMinRanges[ibone].mIndex, mPrevQs[ibone]); + floorPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, mPrevQs[ibone]); } prevDeltaIdx = 0; } else { @@ -340,8 +339,10 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 deltaf; + DeltaSingleQMinRangef minRangef; - DecodeSingleQDelta(mMinRanges[ibone], floorDelta[ibone], deltaf); + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta[ibone].UnQuantize(minRangef, deltaf); mPrevQs[ibone].x += deltaf.x; mPrevQs[ibone].y += deltaf.y; mPrevQs[ibone].z += deltaf.z; @@ -385,7 +386,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas UMath::Vector4 interpq; UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); - DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); + ceilPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, ceilq); interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; @@ -401,8 +402,10 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas UMath::Vector4 ceilq; UMath::Vector4 interpq; UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); + DeltaSingleQMinRangef minRangef; - DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta[ibone].UnQuantize(minRangef, ceilq); ceilq.x += mPrevQs[ibone].x; ceilq.y += mPrevQs[ibone].y; ceilq.z += mPrevQs[ibone].z; @@ -433,11 +436,10 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (!mPrevQs) { unsigned char numBones = deltaQ->mNumBones; - DeltaSingleQMinRange *minRanges = - reinterpret_cast(reinterpret_cast(deltaQ) + 0x10); + DeltaSingleQMinRange *minRanges; - mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaSingleQMinRange); - mBinSize = AlignSize2(deltaQ->mNumBones * ((1 << deltaQ->mBinLengthPower) + 1)); + deltaQ->GetArrays(minRanges, mBins); + mBinSize = deltaQ->GetBinSize(); if (deltaQ->mNumBones != 0) { float eul[3]; @@ -453,7 +455,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { DeltaSingleQMinRangef minRangef; - DecodeSingleQMinRange(mMinRanges[ibone], minRangef); + mMinRanges[ibone].UnQuantize(minRangef); if (minRangef.mIndex == 0) { mPreMultQs[ibone].x = kSingleQFloatZero; @@ -537,7 +539,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorKey < mPrevKey) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - DecodeSingleQPhysical(floorPhys[ibone], mMinRanges[ibone].mIndex, mPrevQs[ibone]); + floorPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, mPrevQs[ibone]); } } prevDeltaIdx = 0; @@ -552,8 +554,10 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 deltaf; + DeltaSingleQMinRangef minRangef; - DecodeSingleQDelta(mMinRanges[ibone], floorDelta[ibone], deltaf); + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta[ibone].UnQuantize(minRangef, deltaf); mPrevQs[ibone].x += deltaf.x; mPrevQs[ibone].y += deltaf.y; mPrevQs[ibone].z += deltaf.z; @@ -599,7 +603,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo UMath::Vector4 interpq; UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); - DecodeSingleQPhysical(ceilPhys[ibone], mMinRanges[ibone].mIndex, ceilq); + ceilPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, ceilq); interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; @@ -617,8 +621,10 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo UMath::Vector4 ceilq; UMath::Vector4 interpq; UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); + DeltaSingleQMinRangef minRangef; - DecodeSingleQDelta(mMinRanges[ibone], ceilDelta[ibone], ceilq); + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta[ibone].UnQuantize(minRangef, ceilq); ceilq.x += mPrevQs[ibone].x; ceilq.y += mPrevQs[ibone].y; ceilq.z += mPrevQs[ibone].z; From 101c4fb102f61bb6ee173ca7ea1e6e8bb0bb0a5f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 01:05:19 +0100 Subject: [PATCH 231/372] 83.71%: inline qfast delta unquantize Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index b5dd5625a..2714eee4d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -234,16 +234,17 @@ inline void FnDeltaQFast::AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *de unsigned char *binData = reinterpret_cast(floorPhys); for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + DeltaQFastDelta *floorDelta = reinterpret_cast(GetQFastDeltaData(deltaQ, binData, iframe)); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 delta; - DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + floorDelta->UnQuantize(mMinRangesf[ibone], delta); prevQs[ibone].x += delta.x; prevQs[ibone].y += delta.y; prevQs[ibone].z += delta.z; prevQs[ibone].w += delta.w; + floorDelta++; } } } @@ -253,16 +254,17 @@ inline void FnDeltaQFast::SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *de unsigned char *binData = reinterpret_cast(floorPhys); for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + DeltaQFastDelta *floorDelta = reinterpret_cast(GetQFastDeltaData(deltaQ, binData, iframe)); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 delta; - DecodeQFastDelta(mMinRangesf[ibone], &deltaData[ibone * 3], delta); + floorDelta->UnQuantize(mMinRangesf[ibone], delta); prevQs[ibone].x -= delta.x; prevQs[ibone].y -= delta.y; prevQs[ibone].z -= delta.z; prevQs[ibone].w -= delta.w; + floorDelta++; } } } From a673aaedce2e9dc12c2a8451550825ead37ed86a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 01:15:53 +0100 Subject: [PATCH 232/372] 83.73%: restore loader typebuf rewrite Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 4484dc4de..ff14d7ce0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -382,27 +382,31 @@ void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { if (sheader->sh_type == SHT_STRTAB) { sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; if (strcmp(kStrTabName, shstrtab + sheader->sh_name) == 0) { - char *stringEntry = reinterpret_cast(sheader->sh_voffset); + char *s = reinterpret_cast(sheader->sh_voffset); int len = sheader->sh_size; const char *TYPE_SEPARATOR = kNamespaceMarker; - char *t; - unsigned int nameLength; + int slen; - h.strtab = stringEntry; + h.strtab = s; while (len > 0) { - if (stringEntry[0] == '_' && stringEntry[1] == '_') { - t = strstr(stringEntry + 2, TYPE_SEPARATOR); + slen = strlen(s); + if (s[0] == '_' && s[1] == '_') { + char *t = strstr(s + 2, TYPE_SEPARATOR); if (t) { + char typebuf[128]; + char *type_separator = t + strlen(TYPE_SEPARATOR); + unsigned int nameLength; + *t = 0; - nameLength = strlen(t + 3); - memmove(stringEntry + nameLength + 2, stringEntry + 2, strlen(stringEntry + 2) + 1); - memmove(stringEntry, t + 3, nameLength + 1); - stringEntry[nameLength + 1] = '\x7F'; + strcpy(typebuf, s + 2); + nameLength = strlen(type_separator); + memmove(s, type_separator, nameLength + 1); + s[nameLength + 1] = '\x7F'; + strcpy(s + nameLength + 2, typebuf); } } - nameLength = strlen(stringEntry) + 1; - stringEntry += nameLength; - len -= nameLength; + s += slen + 1; + len -= slen + 1; } } } else if (sheader->sh_type == SHT_SYMTAB || sheader->sh_type == SHT_STRTAB) { From 87dd38072ea99655c7534a2cb191b660383e71b8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 01:18:50 +0100 Subject: [PATCH 233/372] 83.74%: adjust loader type pointer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index ff14d7ce0..4c75d10d4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -580,11 +580,8 @@ DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { } r.name = &h->strtab[s[i].st_name]; r.type = &r.name[strlen(&h->strtab[s[i].st_name])]; - r.type++; if (r.type[1] == 0x7F) { - r.type++; - } else { - r.type--; + r.type += 2; } r.isInternalRef = (s[i].st_other - 2) > 3; From a571712e9e3871bd725a0ba0fa1d667eb603b446 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 01:21:32 +0100 Subject: [PATCH 234/372] 83.75%: fix loader runtime type walk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 4c75d10d4..a62d2f8ae 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -248,7 +248,6 @@ void DynamicLoader::Resolve() { if (type[1] == 0x7F) { type += 2; } - type += 1; if (strncmp(gRuntimeAllocType, name, strlen(gRuntimeAllocType)) == 0) { Symbol s; const char *stripped_name; From 3dc274be753f239c2b9c6e956f594c7eb87cc365 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:20:06 +0100 Subject: [PATCH 235/372] 83.79%: inline deltaq init helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h | 13 ++++-- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 53 +--------------------- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h | 14 +++++- 3 files changed, 25 insertions(+), 55 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h index 45c4fd0d1..366a160b9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h @@ -120,14 +120,21 @@ struct DeltaQ : public AnimMemoryMap { return result; } - void GetArrays(DeltaQMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQPhysical *&constPhysical) {} + void GetArrays(DeltaQMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQPhysical *&constPhysical) { + unsigned char *memBytes = reinterpret_cast(this) + 0x12; + + minRanges = reinterpret_cast(memBytes); + binStart = &memBytes[mNumBones * sizeof(DeltaQMinRange)]; + constBoneIndices = GetConstBoneIdx(); + constPhysical = GetConstPhysical(); + } int GetBinSize() const { - return AlignSize2((mNumBones * sizeof(DeltaQPhysical)) + ((GetBinLength() - 1) * mNumBones * sizeof(DeltaQDelta))); + return AlignSize2(mNumBones * (((GetBinLength() - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))); } DeltaQMinRange *GetMinRange() { - unsigned char *memBytes = reinterpret_cast(&this[1]); + unsigned char *memBytes = reinterpret_cast(this) + 0x12; return reinterpret_cast(memBytes); } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 74225d605..057228f91 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -94,24 +94,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQ *deltaQ = reinterpret_cast(mpAnim); if (!mBins) { - unsigned char numBones = deltaQ->mNumBones; - DeltaQMinRange *minRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); - - mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); - mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); - mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = deltaQ->GetBinSize(); - - if (deltaQ->mNumBones != 0) { - UMath::Vector4 *prevQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - - mMinRanges = minRanges; - mPrevQBlock = prevQs; - mPrevQs = prevQs; - } else { - mMinRanges = minRanges; - } + InitBuffersAsRequired(); } int floorTime = FloatToInt(currTime); int floorKey; @@ -315,24 +298,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq DeltaQ *deltaQ = reinterpret_cast(mpAnim); if (!mBins) { - unsigned char numBones = deltaQ->mNumBones; - DeltaQMinRange *minRanges = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); - - mBins = reinterpret_cast(minRanges) + numBones * sizeof(DeltaQMinRange); - mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); - mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); - mBinSize = deltaQ->GetBinSize(); - - if (deltaQ->mNumBones != 0) { - UMath::Vector4 *prevQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - - mMinRanges = minRanges; - mPrevQBlock = prevQs; - mPrevQs = prevQs; - } else { - mMinRanges = minRanges; - } + InitBuffersAsRequired(); } int floorTime = FloatToInt(currTime); int floorKey; @@ -580,19 +546,4 @@ bool FnDeltaQ::EvalVel2D(float currTime, float *vel) { return true; } -void FnDeltaQ::InitBuffersAsRequired() { - DeltaQ *deltaQ = reinterpret_cast(mpAnim); - - mMinRanges = GetMinRanges(deltaQ); - mBins = GetBinStart(deltaQ); - mBinSize = deltaQ->GetBinSize(); - mConstBoneIdxs = deltaQ->GetConstBoneIdx(); - mConstPhysical = deltaQ->GetConstPhysical(); - - if (deltaQ->mNumBones != 0) { - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; - } -} - }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h index 7d86a0011..a8c4203db 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h @@ -73,7 +73,19 @@ class FnDeltaQ : public FnAnimMemoryMap { protected: virtual bool EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt); - void InitBuffersAsRequired(); + void InitBuffersAsRequired() { + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + DeltaQMinRange *minRanges; + + deltaQ->GetArrays(minRanges, mBins, mConstBoneIdxs, mConstPhysical); + mBinSize = deltaQ->GetBinSize(); + + if (deltaQ->mNumBones != 0) { + mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); + mPrevQBlock = mPrevQs; + } + mMinRanges = minRanges; + } DeltaQMinRange *mMinRanges; // offset 0x10, size 0x4 unsigned char *mBins; // offset 0x14, size 0x4 From 3e3847d65213b77c5c30a0644b3006cdb028e554 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:20:53 +0100 Subject: [PATCH 236/372] 83.80%: tune deltaq init store order Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h index a8c4203db..7612f418a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h @@ -81,10 +81,12 @@ class FnDeltaQ : public FnAnimMemoryMap { mBinSize = deltaQ->GetBinSize(); if (deltaQ->mNumBones != 0) { - mPrevQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs))); - mPrevQBlock = mPrevQs; + mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; + } else { + mMinRanges = minRanges; } - mMinRanges = minRanges; } DeltaQMinRange *mMinRanges; // offset 0x10, size 0x4 From 771f4eadb6a82be8ddca5c4ab6bca6cb9d941d93 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:22:31 +0100 Subject: [PATCH 237/372] 84.04%: use cached deltaq bin buffers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 057228f91..b12168e9e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -137,7 +137,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { int floorDeltaIdx = floorKey & binLenModMask; int prevBinIdx = mPrevKey >> binLenPower; int prevDeltaIdx; - unsigned char *binData = GetBin(deltaQ, floorBinIdx); + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaQPhysical *floorPhys = GetPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); @@ -225,7 +225,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { int ceilBinIdx = ceilKey >> binLenPower; - DeltaQPhysical *ceilPhys = GetPhysical(GetBin(deltaQ, ceilBinIdx)); + DeltaQPhysical *ceilPhys = GetPhysical(&mBins[ceilBinIdx * mBinSize]); if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { @@ -341,7 +341,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq int floorDeltaIdx = floorKey & binLenModMask; int prevBinIdx = mPrevKey >> binLenPower; int prevDeltaIdx; - unsigned char *binData = GetBin(deltaQ, floorBinIdx); + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaQPhysical *floorPhys = GetPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || @@ -438,7 +438,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { int ceilBinIdx = ceilKey >> binLenPower; - DeltaQPhysical *ceilPhys = GetPhysical(GetBin(deltaQ, ceilBinIdx)); + DeltaQPhysical *ceilPhys = GetPhysical(&mBins[ceilBinIdx * mBinSize]); if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { From 55e981f3d0cc63da0e6ec0416e53945173e8afb1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:28:00 +0100 Subject: [PATCH 238/372] 84.22%: decode deltaq xyz directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 32 ++++++++++++++++------ 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index b12168e9e..ad6c702c8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -144,7 +144,9 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - floorPhys[ibone].UnQuantize(mPrevQs[ibone]); + mPrevQs[ibone].x = floorPhys[ibone].mX * 6.1037019e-5f - 1.0f; + mPrevQs[ibone].y = floorPhys[ibone].mY * 3.0518044e-5f - 1.0f; + mPrevQs[ibone].z = floorPhys[ibone].mZ * 3.0518044e-5f - 1.0f; } prevDeltaIdx = 0; } else { @@ -160,7 +162,9 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - floorDelta->UnQuantize(minRangef, deltaf); + deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; + deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; + deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; mPrevQs[ibone].x += deltaf.x; mPrevQs[ibone].y += deltaf.y; mPrevQs[ibone].z += deltaf.z; @@ -176,7 +180,9 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - floorDelta->UnQuantize(minRangef, deltaf); + deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; + deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; + deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; mPrevQs[ibone].x -= deltaf.x; mPrevQs[ibone].y -= deltaf.y; mPrevQs[ibone].z -= deltaf.z; @@ -265,7 +271,9 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - ceilDelta->UnQuantize(minRangef, deltaf); + deltaf.x = minRangef.mMin.x + minRangef.mRange.x * ceilDelta->mX; + deltaf.y = minRangef.mMin.y + minRangef.mRange.y * ceilDelta->mY; + deltaf.z = minRangef.mMin.z + minRangef.mRange.z * ceilDelta->mZ; ceilq.x = mPrevQs[ibone].x + deltaf.x; ceilq.y = mPrevQs[ibone].y + deltaf.y; ceilq.z = mPrevQs[ibone].z + deltaf.z; @@ -348,7 +356,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - floorPhys[ibone].UnQuantize(mPrevQs[ibone]); + mPrevQs[ibone].x = floorPhys[ibone].mX * 6.1037019e-5f - 1.0f; + mPrevQs[ibone].y = floorPhys[ibone].mY * 3.0518044e-5f - 1.0f; + mPrevQs[ibone].z = floorPhys[ibone].mZ * 3.0518044e-5f - 1.0f; } } prevDeltaIdx = 0; @@ -366,7 +376,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - floorDelta->UnQuantize(minRangef, deltaf); + deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; + deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; + deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; mPrevQs[ibone].x += deltaf.x; mPrevQs[ibone].y += deltaf.y; mPrevQs[ibone].z += deltaf.z; @@ -384,7 +396,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - floorDelta->UnQuantize(minRangef, deltaf); + deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; + deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; + deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; mPrevQs[ibone].x -= deltaf.x; mPrevQs[ibone].y -= deltaf.y; mPrevQs[ibone].z -= deltaf.z; @@ -484,7 +498,9 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - ceilDelta->UnQuantize(minRangef, deltaf); + deltaf.x = minRangef.mMin.x + minRangef.mRange.x * ceilDelta->mX; + deltaf.y = minRangef.mMin.y + minRangef.mRange.y * ceilDelta->mY; + deltaf.z = minRangef.mMin.z + minRangef.mRange.z * ceilDelta->mZ; ceilq.x = mPrevQs[ibone].x + deltaf.x; ceilq.y = mPrevQs[ibone].y + deltaf.y; ceilq.z = mPrevQs[ibone].z + deltaf.z; From f0255dc7481a22819e5e023465c78be542b53157 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:31:17 +0100 Subject: [PATCH 239/372] 84.44%: use cached singleq bin buffers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index fcc33cdce..8182ede62 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -320,7 +320,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas int floorDeltaIdx = floorKey & binLenModMask; int prevBinIdx = mPrevKey >> binLenPower; int prevDeltaIdx; - unsigned char *binData = GetSingleQBin(deltaQ, floorBinIdx); + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaSingleQPhysical *floorPhys = GetSingleQPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; @@ -378,7 +378,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { int ceilBinIdx = ceilKey >> binLenPower; - DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(GetSingleQBin(deltaQ, ceilBinIdx)); + DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(&mBins[ceilBinIdx * mBinSize]); if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { @@ -532,7 +532,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo int floorDeltaIdx = floorKey & binLenModMask; int prevBinIdx = mPrevKey >> binLenPower; int prevDeltaIdx; - unsigned char *binData = GetSingleQBin(deltaQ, floorBinIdx); + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaSingleQPhysical *floorPhys = GetSingleQPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; @@ -594,7 +594,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { int ceilBinIdx = ceilKey >> binLenPower; - DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(GetSingleQBin(deltaQ, ceilBinIdx)); + DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(&mBins[ceilBinIdx * mBinSize]); if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { From 157d24b17dfb5c6fa3eee549b4772f80c16947b0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:42:12 +0100 Subject: [PATCH 240/372] 84.49%: factor singleq init helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 169 ++++++------------ 1 file changed, 59 insertions(+), 110 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 8182ede62..ab40b3ad4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -216,6 +216,63 @@ void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { EvalSQTMasked(currTime, nullptr, sqt); } +inline void FnDeltaSingleQ::InitBuffersAsRequired() { + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + unsigned char numBones = deltaQ->mNumBones; + DeltaSingleQMinRange *minRanges; + + deltaQ->GetArrays(minRanges, mBins); + mBinSize = deltaQ->GetBinSize(); + + if (numBones != 0) { + float eul[3]; + + mPrevQBlock = MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; + mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPreMultQs))); + mPostMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPostMultQs))); + + for (int ibone = 0; ibone < numBones; ibone++) { + DeltaSingleQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + + if (minRangef.mIndex == 0) { + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; + eul[0] = kSingleQFloatZero; + eul[1] = minRangef.mConst0; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (minRangef.mIndex == 1) { + eul[0] = minRangef.mConst0; + eul[1] = kSingleQFloatZero; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + + eul[0] = kSingleQFloatZero; + eul[1] = kSingleQFloatZero; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else { + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; + eul[0] = minRangef.mConst0; + eul[1] = minRangef.mConst1; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + } + } + } else { + mMinRanges = minRanges; + } +} + bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (boneMask) { return EvalSQTMasked(currTime, boneMask, sqt); @@ -223,61 +280,7 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); if (!mPrevQs) { - unsigned char numBones = deltaQ->mNumBones; - DeltaSingleQMinRange *minRanges; - - deltaQ->GetArrays(minRanges, mBins); - mBinSize = deltaQ->GetBinSize(); - - if (deltaQ->mNumBones != 0) { - float eul[3]; - - mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); - mPrevQs = reinterpret_cast(mPrevQBlock); - mMinRanges = minRanges; - mPreMultQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); - mPostMultQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); - - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DeltaSingleQMinRangef minRangef; - - mMinRanges[ibone].UnQuantize(minRangef); - - if (minRangef.mIndex == 0) { - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - eul[0] = kSingleQFloatZero; - eul[1] = minRangef.mConst0; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else if (minRangef.mIndex == 1) { - eul[0] = minRangef.mConst0; - eul[1] = kSingleQFloatZero; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - - eul[0] = kSingleQFloatZero; - eul[1] = kSingleQFloatZero; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else { - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - eul[0] = minRangef.mConst0; - eul[1] = minRangef.mConst1; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - } - } - } else { - mMinRanges = minRanges; - } + InitBuffersAsRequired(); } int floorTime = FloatToInt(currTime); int floorKey; @@ -435,61 +438,7 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); if (!mPrevQs) { - unsigned char numBones = deltaQ->mNumBones; - DeltaSingleQMinRange *minRanges; - - deltaQ->GetArrays(minRanges, mBins); - mBinSize = deltaQ->GetBinSize(); - - if (deltaQ->mNumBones != 0) { - float eul[3]; - - mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); - mPrevQs = reinterpret_cast(mPrevQBlock); - mMinRanges = minRanges; - mPreMultQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); - mPostMultQs = - reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); - - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - DeltaSingleQMinRangef minRangef; - - mMinRanges[ibone].UnQuantize(minRangef); - - if (minRangef.mIndex == 0) { - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - eul[0] = kSingleQFloatZero; - eul[1] = minRangef.mConst0; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else if (minRangef.mIndex == 1) { - eul[0] = minRangef.mConst0; - eul[1] = kSingleQFloatZero; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - - eul[0] = kSingleQFloatZero; - eul[1] = kSingleQFloatZero; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else { - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - eul[0] = minRangef.mConst0; - eul[1] = minRangef.mConst1; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - } - } - } else { - mMinRanges = minRanges; - } + InitBuffersAsRequired(); } int floorTime = FloatToInt(currTime); int floorKey; From 7206611b3f3f4417a42570fd6555070ef2b5b352 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 09:45:41 +0100 Subject: [PATCH 241/372] 84.51%: inline singleq min-range base math Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index ab40b3ad4..daed8bfd0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -221,7 +221,8 @@ inline void FnDeltaSingleQ::InitBuffersAsRequired() { unsigned char numBones = deltaQ->mNumBones; DeltaSingleQMinRange *minRanges; - deltaQ->GetArrays(minRanges, mBins); + minRanges = reinterpret_cast(&deltaQ[1]); + mBins = &reinterpret_cast(minRanges)[numBones * sizeof(DeltaSingleQMinRange)]; mBinSize = deltaQ->GetBinSize(); if (numBones != 0) { From a8a90e8e22a0c11cba08713c8cae2981a6e313cd Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:05:30 +0100 Subject: [PATCH 242/372] 84.64%: remove singleq zero-bone init branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 90 +++++++++---------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index daed8bfd0..587644767 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -217,60 +217,54 @@ void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { } inline void FnDeltaSingleQ::InitBuffersAsRequired() { + float eul[3]; + int ibone; DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); - unsigned char numBones = deltaQ->mNumBones; DeltaSingleQMinRange *minRanges; minRanges = reinterpret_cast(&deltaQ[1]); - mBins = &reinterpret_cast(minRanges)[numBones * sizeof(DeltaSingleQMinRange)]; + mBins = &reinterpret_cast(minRanges)[deltaQ->mNumBones * sizeof(DeltaSingleQMinRange)]; mBinSize = deltaQ->GetBinSize(); - - if (numBones != 0) { - float eul[3]; - - mPrevQBlock = MemoryPoolManager::NewBlock(numBones * sizeof(*mPrevQs)); - mPrevQs = reinterpret_cast(mPrevQBlock); - mMinRanges = minRanges; - mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPreMultQs))); - mPostMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(numBones * sizeof(*mPostMultQs))); - - for (int ibone = 0; ibone < numBones; ibone++) { - DeltaSingleQMinRangef minRangef; - - mMinRanges[ibone].UnQuantize(minRangef); - - if (minRangef.mIndex == 0) { - mPreMultQs[ibone].x = kSingleQFloatZero; - mPreMultQs[ibone].y = kSingleQFloatZero; - mPreMultQs[ibone].z = kSingleQFloatZero; - mPreMultQs[ibone].w = kSingleQFloatOne; - eul[0] = kSingleQFloatZero; - eul[1] = minRangef.mConst0; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else if (minRangef.mIndex == 1) { - eul[0] = minRangef.mConst0; - eul[1] = kSingleQFloatZero; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - - eul[0] = kSingleQFloatZero; - eul[1] = kSingleQFloatZero; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else { - mPostMultQs[ibone].x = kSingleQFloatZero; - mPostMultQs[ibone].y = kSingleQFloatZero; - mPostMultQs[ibone].z = kSingleQFloatZero; - mPostMultQs[ibone].w = kSingleQFloatOne; - eul[0] = minRangef.mConst0; - eul[1] = minRangef.mConst1; - eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); - } + mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; + mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); + mPostMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); + + for (ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaSingleQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + + if (minRangef.mIndex == 0) { + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; + eul[0] = kSingleQFloatZero; + eul[1] = minRangef.mConst0; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (minRangef.mIndex == 1) { + eul[0] = minRangef.mConst0; + eul[1] = kSingleQFloatZero; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + + eul[0] = kSingleQFloatZero; + eul[1] = kSingleQFloatZero; + eul[2] = minRangef.mConst1; + SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else { + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; + eul[0] = minRangef.mConst0; + eul[1] = minRangef.mConst1; + eul[2] = kSingleQFloatZero; + SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); } - } else { - mMinRanges = minRanges; } } From a496194cc326c3160fac3d36676f524538376c1b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:11:20 +0100 Subject: [PATCH 243/372] 84.71%: drop singleq outq locals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 587644767..2f2e6b9f6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -382,7 +382,6 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); ceilPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, ceilq); interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; @@ -391,7 +390,8 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); } } else { DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); @@ -399,7 +399,6 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); DeltaSingleQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); @@ -415,14 +414,14 @@ bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMas interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); } } } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); - - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], *outq); + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); } } @@ -545,7 +544,6 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); ceilPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, ceilq); interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; @@ -554,7 +552,8 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); } } } else { @@ -564,7 +563,6 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; UMath::Vector4 interpq; - UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); DeltaSingleQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); @@ -580,16 +578,16 @@ bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, flo interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; NormalizeSingleQQuat(interpq); - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], *outq); + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); } } } } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 *outq = reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone])); - - ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], *outq); + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); } } } From 2ecdbdd9766f7d83d873e86978989efa1802df59 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:29:57 +0100 Subject: [PATCH 244/372] 84.72%: return poseblender early eval directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 277e0c4f6..40fea7d6c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -203,10 +203,7 @@ void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask *boneMask) { if (currentTime <= mStartTransTime) { - if (mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], sqtBuffer, boneMask)) { - return true; - } - return false; + return mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], sqtBuffer, boneMask); } if (currentTime >= mEndTransTime) { From ecd811460b56e495ac9007c6c82113b34ff9bb88 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:32:37 +0100 Subject: [PATCH 245/372] 84.73%: localize poseblender bone-count loads Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 40fea7d6c..7c5341f22 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -233,11 +233,10 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask return false; } - int numBones = mpSkel->GetNumBones(); if (mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { if (!boneMask) { if (mAlignRootBoneIdx >= 0) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { if (boneIdx == mAlignRootBoneIdx) { EAGL4::Transform rootTransform; @@ -250,7 +249,7 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); } } else { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { int poseIdx = boneIdx * 12; FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); @@ -259,7 +258,7 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); } } else if (mAlignRootBoneIdx >= 0) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { if (boneMask->GetBone(boneIdx)) { if (boneIdx == mAlignRootBoneIdx) { EAGL4::Transform rootTransform; @@ -274,7 +273,7 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask } } } else { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { if (boneMask->GetBone(boneIdx)) { int poseIdx = boneIdx * 12; From 01ac79580e7306bf727a6db28c7f62219299fec7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:34:29 +0100 Subject: [PATCH 246/372] 84.79%: use poseblender root index member Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 7c5341f22..3ad74c0be 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -240,8 +240,8 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask if (boneIdx == mAlignRootBoneIdx) { EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], mAlignRootBoneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); } int poseIdx = boneIdx * 12; @@ -263,8 +263,8 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask if (boneIdx == mAlignRootBoneIdx) { EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, boneIdx); + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], mAlignRootBoneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); } int poseIdx = boneIdx * 12; From 42674ac661a54c7fda140bad654700589b72e55f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:35:35 +0100 Subject: [PATCH 247/372] 84.82%: mirror poseblender root index fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 3ad74c0be..6d0b72061 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -319,16 +319,15 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos } mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], mPose[0]); - int numBones = mpSkel->GetNumBones(); mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], mPose[1]); if (mAlignRootBoneIdx >= 0) { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { if (boneIdx == mAlignRootBoneIdx) { EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], boneIdx); - BlendRootTranslation(w, mPose[0], mPose[1], outputPose, boneIdx); + ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], mAlignRootBoneIdx); + BlendRootTranslation(w, mPose[0], mPose[1], outputPose, mAlignRootBoneIdx); } int poseIdx = boneIdx * 12; @@ -336,7 +335,7 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); } } else { - for (int boneIdx = numBones - 1; boneIdx >= 0; --boneIdx) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { int poseIdx = boneIdx * 12; FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); From 993c205856b4bec41e40d73fcd0b85727b3526a6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:37:38 +0100 Subject: [PATCH 248/372] 84.87%: use pose-index tail loop in poseblender eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 6d0b72061..608010efc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -335,10 +335,8 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); } } else { - for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { - int poseIdx = boneIdx * 12; - - FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); + for (int poseIdx = mpSkel->GetNumBones() * 12 - 8; poseIdx > 3; poseIdx -= 12) { + FastQuatBlendF4(w, &mPose[0][poseIdx], &mPose[1][poseIdx], &outputPose[poseIdx]); } BlendRootTranslation(w, mPose[0], mPose[1], outputPose, 0); From 9424f7c76a8a99d3f615209b2acad0b9c0b1632b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:41:25 +0100 Subject: [PATCH 249/372] 84.88%: reverse qfast mask delta walk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 2714eee4d..5d22a75bf 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -333,9 +333,9 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta unsigned char *boneIdxs = deltaQ->mBoneIdxs; for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe) + (deltaQ->mNumBones - 1) * 3; - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; @@ -345,7 +345,7 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta prevQs[ibone].z -= delta.z; prevQs[ibone].w -= delta.w; } - deltaData += 3; + deltaData -= 3; } } } From 5e0b5c80a8202c3f9e17ceefd151e8fb2ac48c26 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:43:59 +0100 Subject: [PATCH 250/372] 84.94%: reuse qfast mask delta frame pointer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 5d22a75bf..ac9d91a6d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -308,11 +308,11 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta const BoneMask *boneMask) { unsigned char *binData = reinterpret_cast(floorPhys); unsigned char *boneIdxs = deltaQ->mBoneIdxs; + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx); + unsigned char numBones = deltaQ->mNumBones; for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe); - - for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + for (int ibone = 0; ibone < numBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; From d1567dcb849ad579aa237c90fea0587da413ac50 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:44:37 +0100 Subject: [PATCH 251/372] 84.96%: reuse qfast reverse mask pointer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index ac9d91a6d..da874199a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -331,11 +331,11 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta const BoneMask *boneMask) { unsigned char *binData = reinterpret_cast(floorPhys); unsigned char *boneIdxs = deltaQ->mBoneIdxs; + unsigned char numBones = deltaQ->mNumBones; + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx - 1) + (numBones - 1) * 3; for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, iframe) + (deltaQ->mNumBones - 1) * 3; - - for (int ibone = deltaQ->mNumBones - 1; ibone >= 0; ibone--) { + for (int ibone = numBones - 1; ibone >= 0; ibone--) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; From b3c13646855650124673760b9f5a6b684563fe4f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:45:41 +0100 Subject: [PATCH 252/372] 84.97%: reload qfast mask bone counts per frame Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index da874199a..84844fe34 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -309,9 +309,10 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta unsigned char *binData = reinterpret_cast(floorPhys); unsigned char *boneIdxs = deltaQ->mBoneIdxs; unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx); - unsigned char numBones = deltaQ->mNumBones; for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + unsigned char numBones = deltaQ->mNumBones; + for (int ibone = 0; ibone < numBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; @@ -331,10 +332,11 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta const BoneMask *boneMask) { unsigned char *binData = reinterpret_cast(floorPhys); unsigned char *boneIdxs = deltaQ->mBoneIdxs; - unsigned char numBones = deltaQ->mNumBones; - unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx - 1) + (numBones - 1) * 3; + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx - 1) + (deltaQ->mNumBones - 1) * 3; for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + unsigned char numBones = deltaQ->mNumBones; + for (int ibone = numBones - 1; ibone >= 0; ibone--) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; From 6ab71703298ea5eea79c9eab6680aa97a5238d16 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:48:17 +0100 Subject: [PATCH 253/372] 84.98%: add qfast mask zero-bone guard Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 84844fe34..547de95b5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -608,7 +608,13 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM DeltaQFast *deltaQ = reinterpret_cast(mpAnim); float *quatBase = sqt + 4; + unsigned char numBones = deltaQ->mNumBones; unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (!numBones) { + return true; + } + int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); From 31ba94e70909f0059ec755fb68ed76e7ab48efb2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:50:08 +0100 Subject: [PATCH 254/372] 85.01%: add deltaq masked zero-bone guard Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index ad6c702c8..179ecb35f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -304,6 +304,12 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { DeltaQ *deltaQ = reinterpret_cast(mpAnim); + unsigned char numBones = deltaQ->mNumBones; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (!numBones) { + return true; + } if (!mBins) { InitBuffersAsRequired(); @@ -351,7 +357,6 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq int prevDeltaIdx; unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaQPhysical *floorPhys = GetPhysical(binData); - unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { From f28c1279bca5554067282c19173fb63988c66115 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:52:17 +0100 Subject: [PATCH 255/372] 85.03%: reorder deltaq masked entry setup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 179ecb35f..7565fac3d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -303,6 +303,9 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { } bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + if (!mBins) { + InitBuffersAsRequired(); + } DeltaQ *deltaQ = reinterpret_cast(mpAnim); unsigned char numBones = deltaQ->mNumBones; unsigned char *boneIdxs = deltaQ->mBoneIdxs; @@ -310,10 +313,6 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq if (!numBones) { return true; } - - if (!mBins) { - InitBuffersAsRequired(); - } int floorTime = FloatToInt(currTime); int floorKey; From 764764a2cbac607ad5efcc85420ad77054c05559 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 10:57:21 +0100 Subject: [PATCH 256/372] 85.08%: decode qfast next physical branch inline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 547de95b5..8f67c863b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -279,10 +279,18 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx if (ceilBinIdx != floorBinIdx) { unsigned char numBones = deltaQ->mNumBones; - DeltaQFastPhysical *ceilPhys = reinterpret_cast(binData); + DeltaQFastPhysical *physical = reinterpret_cast(binData); for (int ibone = 0; ibone < numBones; ibone++) { - ceilPhys[ibone].UnQuantize(mNextQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + + nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - + kQFastPhysicalBias12; + physical++; } } else { unsigned int numBones = deltaQ->mNumBones; From 8c69a9de7956e0bfd208eaeeaab518db776fe0f2 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:00:03 +0100 Subject: [PATCH 257/372] 85.18%: use qfast masked ceil temp decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 8f67c863b..bdb7c9284 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -390,26 +390,20 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi } } else { unsigned int numBones = deltaQ->mNumBones; - unsigned char *deltaData = &binData[numBones * 6 + floorDeltaIdx * numBones * 3]; + DeltaQFastDelta *ceilDelta = reinterpret_cast(&binData[numBones * 6 + floorDeltaIdx * numBones * 3]); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float *minRange = reinterpret_cast(&mMinRangesf[ibone]); - unsigned char b0 = deltaData[0]; - unsigned char b1 = deltaData[1]; - unsigned char b2 = deltaData[2]; - - nextQ[0] = minRange[4] * static_cast(b0 >> 2) * kQFastDeltaScale6 + minRange[0] + prevQ[0]; - nextQ[1] = minRange[5] * static_cast(b1 >> 2) * kQFastDeltaScale6 + minRange[1] + prevQ[1]; - nextQ[2] = minRange[6] * static_cast(b2 >> 2) * kQFastDeltaScale6 + minRange[2] + prevQ[2]; - nextQ[3] = - minRange[7] * static_cast(static_cast(((b0 & 3) * 0x10) + ((b1 & 3) * 4) | (b2 & 3))) * kQFastDeltaScale6 + - minRange[3] + prevQ[3]; + UMath::Vector4 ceilq; + + ceilDelta->UnQuantize(mMinRangesf[ibone], ceilq); + mNextQs[ibone].x = ceilq.x + mPrevQs[ibone].x; + mNextQs[ibone].y = ceilq.y + mPrevQs[ibone].y; + mNextQs[ibone].z = ceilq.z + mPrevQs[ibone].z; + mNextQs[ibone].w = ceilq.w + mPrevQs[ibone].w; } - deltaData += 3; + ceilDelta++; } } From 0162a7f1e596f003b8a21d7376c13ec1e3590982 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:11:25 +0100 Subject: [PATCH 258/372] 85.19%: use qfast floor-key helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 56 ++----------------- 1 file changed, 6 insertions(+), 50 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index bdb7c9284..13e489b84 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -423,56 +423,12 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned char *boneIdxs = deltaQ->mBoneIdxs; if (deltaQ->mNumBones) { - int floorTime = FloatToInt(currTime); - unsigned short *times = deltaQ->mTimes; - int floorKey; - unsigned int floorDeltaIdx; - int floorBinIdx; - unsigned int binLengthMask; - - if (!times) { - if (floorTime < 0) { - floorKey = 0; - } else if (deltaQ->mNumKeys > floorTime) { - floorKey = floorTime; - } else { - floorKey = deltaQ->mNumKeys - 1; - } - } else { - if (floorTime < times[0]) { - floorKey = 0; - } else { - int timeIndex = mPrevKey - 1; - if (mPrevKey < 1) { - timeIndex = 0; - } - - if (floorTime >= times[timeIndex]) { - int lastTimeIndex = deltaQ->mNumKeys - 2; - if (timeIndex < lastTimeIndex) { - unsigned int nextTime = times[timeIndex + 1]; - int nextIndex = timeIndex; - - while (((timeIndex = nextIndex), static_cast(nextTime) <= floorTime && - ((timeIndex = nextIndex + 1), timeIndex < lastTimeIndex))) { - nextTime = times[nextIndex + 2]; - nextIndex = timeIndex; - } - } - } else { - if (timeIndex > 0) { - do { - timeIndex--; - if (timeIndex < 1) { - break; - } - } while (floorTime < times[timeIndex]); - } - } - - floorKey = timeIndex + 1; - } - } + int floorTime = FloatToInt(currTime); + unsigned short *times = deltaQ->mTimes; + int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); + unsigned int floorDeltaIdx; + int floorBinIdx; + unsigned int binLengthMask; floorBinIdx = static_cast(floorKey) >> deltaQ->mBinLengthPower; binLengthMask = 0x7FFFFFFFU >> (0x1F - deltaQ->mBinLengthPower); From 97eead7566f11ff50a068a299cd1aba114660dea Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:15:22 +0100 Subject: [PATCH 259/372] 85.21%: index qfast physical decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 13e489b84..82102b0c9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -279,10 +279,10 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx if (ceilBinIdx != floorBinIdx) { unsigned char numBones = deltaQ->mNumBones; - DeltaQFastPhysical *physical = reinterpret_cast(binData); for (int ibone = 0; ibone < numBones; ibone++) { float *nextQ = reinterpret_cast(&mNextQs[ibone]); + DeltaQFastPhysical *physical = reinterpret_cast(&binData[ibone * 6]); nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; @@ -290,7 +290,6 @@ void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx nextQ[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - physical++; } } else { unsigned int numBones = deltaQ->mNumBones; From 0cab88acb8d062c411f78997c5fb351376bcc1ce Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:17:00 +0100 Subject: [PATCH 260/372] 85.23%: index masked qfast decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 82102b0c9..d1a1ff196 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -370,12 +370,12 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi if (ceilBinIdx != floorBinIdx) { unsigned char numBones = deltaQ->mNumBones; - DeltaQFastPhysical *physical = reinterpret_cast(binData); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { float *nextQ = reinterpret_cast(&mNextQs[ibone]); + DeltaQFastPhysical *physical = reinterpret_cast(&binData[ibone * 6]); nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; @@ -385,7 +385,6 @@ void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBi kQFastPhysicalScale12 - kQFastPhysicalBias12; } - physical++; } } else { unsigned int numBones = deltaQ->mNumBones; From 44348ebb5ee1e939ca2c0618e761c2f428aaefd9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:18:44 +0100 Subject: [PATCH 261/372] 85.28%: type qfast masked delta decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index d1a1ff196..79db80ec2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -323,8 +323,9 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta for (int ibone = 0; ibone < numBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; + DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); - DecodeQFastDelta(mMinRangesf[ibone], deltaData, delta); + floorDelta->UnQuantize(mMinRangesf[ibone], delta); prevQs[ibone].x += delta.x; prevQs[ibone].y += delta.y; prevQs[ibone].z += delta.z; @@ -347,8 +348,9 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta for (int ibone = numBones - 1; ibone >= 0; ibone--) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; + DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); - DecodeQFastDelta(mMinRangesf[ibone], deltaData, delta); + floorDelta->UnQuantize(mMinRangesf[ibone], delta); prevQs[ibone].x -= delta.x; prevQs[ibone].y -= delta.y; prevQs[ibone].z -= delta.z; From b699552b3f12930fe84a0f7278dc6e2cdbc9d1a7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:20:18 +0100 Subject: [PATCH 262/372] 85.42%: walk qfast masked deltas by type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 79db80ec2..6c47e8fba 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -319,11 +319,11 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { unsigned char numBones = deltaQ->mNumBones; + DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); for (int ibone = 0; ibone < numBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; - DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); floorDelta->UnQuantize(mMinRangesf[ibone], delta); prevQs[ibone].x += delta.x; @@ -331,8 +331,9 @@ void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta prevQs[ibone].z += delta.z; prevQs[ibone].w += delta.w; } - deltaData += 3; + floorDelta++; } + deltaData = reinterpret_cast(floorDelta); } } @@ -344,11 +345,11 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { unsigned char numBones = deltaQ->mNumBones; + DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); for (int ibone = numBones - 1; ibone >= 0; ibone--) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 delta; - DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); floorDelta->UnQuantize(mMinRangesf[ibone], delta); prevQs[ibone].x -= delta.x; @@ -356,8 +357,9 @@ void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *delta prevQs[ibone].z -= delta.z; prevQs[ibone].w -= delta.w; } - deltaData -= 3; + floorDelta--; } + deltaData = reinterpret_cast(floorDelta); } } From 953353d26bc5b9697693b84daa7974a0ebd94010 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:38:43 +0100 Subject: [PATCH 263/372] 85.43%: walk statelessq masked records Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index f824deea7..3e5fd2eb5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -220,16 +220,17 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool if (boneMask->GetBone(boneIdx)) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - int frameIndex = ibone * 4; - - prevQ.x = UncompressStatelessQValue(frameData[frameIndex + 0]); - prevQ.y = UncompressStatelessQValue(frameData[frameIndex + 1]); - prevQ.z = UncompressStatelessQValue(frameData[frameIndex + 2]); - prevQ.w = UncompressStatelessQValue(frameData[frameIndex + 3]); - nextQ.x = UncompressStatelessQValue(nextFrameData[frameIndex + 0]); - nextQ.y = UncompressStatelessQValue(nextFrameData[frameIndex + 1]); - nextQ.z = UncompressStatelessQValue(nextFrameData[frameIndex + 2]); - nextQ.w = UncompressStatelessQValue(nextFrameData[frameIndex + 3]); + unsigned short *currFrame = &frameData[ibone * 4]; + unsigned short *nextFrame = &nextFrameData[ibone * 4]; + + prevQ.x = UncompressStatelessQValue(*currFrame++); + prevQ.y = UncompressStatelessQValue(*currFrame++); + prevQ.z = UncompressStatelessQValue(*currFrame++); + prevQ.w = UncompressStatelessQValue(*currFrame); + nextQ.x = UncompressStatelessQValue(*nextFrame++); + nextQ.y = UncompressStatelessQValue(*nextFrame++); + nextQ.z = UncompressStatelessQValue(*nextFrame++); + nextQ.w = UncompressStatelessQValue(*nextFrame); index = boneIdx * 12; sqt[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; @@ -243,14 +244,14 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - int frameIndex = ibone * 4; + unsigned short *currFrame = &frameData[ibone * 4]; index = boneIdx * 12; - sqt[index + 0] = UncompressStatelessQValue(frameData[frameIndex + 0]); - sqt[index + 1] = UncompressStatelessQValue(frameData[frameIndex + 1]); - sqt[index + 2] = UncompressStatelessQValue(frameData[frameIndex + 2]); - sqt[index + 3] = UncompressStatelessQValue(frameData[frameIndex + 3]); + sqt[index + 0] = UncompressStatelessQValue(*currFrame++); + sqt[index + 1] = UncompressStatelessQValue(*currFrame++); + sqt[index + 2] = UncompressStatelessQValue(*currFrame++); + sqt[index + 3] = UncompressStatelessQValue(*currFrame); } } } @@ -264,14 +265,14 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned char boneIdx = constIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - int frameIndex = ibone * 4; + unsigned short *currFrame = &constBuf[ibone * 4]; index = boneIdx * 12; - sqt[index + 0] = UncompressStatelessQValue(constBuf[frameIndex + 0]); - sqt[index + 1] = UncompressStatelessQValue(constBuf[frameIndex + 1]); - sqt[index + 2] = UncompressStatelessQValue(constBuf[frameIndex + 2]); - sqt[index + 3] = UncompressStatelessQValue(constBuf[frameIndex + 3]); + sqt[index + 0] = UncompressStatelessQValue(*currFrame++); + sqt[index + 1] = UncompressStatelessQValue(*currFrame++); + sqt[index + 2] = UncompressStatelessQValue(*currFrame++); + sqt[index + 3] = UncompressStatelessQValue(*currFrame); } } } From b72e3b0bb7d9dfc4c2b2a03eb5265aa2c80c3073 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:43:04 +0100 Subject: [PATCH 264/372] 85.44%: reorder rawlinear eval helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawLinearChannel.h | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h index fa3d6d3e9..33c6ad57b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h @@ -76,6 +76,18 @@ class RawLinearChannel : public AnimMemoryMap { } } + private: + void EvalFrame(int frame, float *output) { + const unsigned short *dofIndex = GetDOFIndex(); + const float *frameData = GetFrame(frame); + unsigned short numDOFs = mNumDOFs; + + for (int i = 0; i < numDOFs; i++) { + output[*dofIndex++] = *frameData++; + } + } + + public: void Eval(float frameTime, float *output, bool interp) { int frame = static_cast(frameTime); @@ -98,17 +110,6 @@ class RawLinearChannel : public AnimMemoryMap { } } - private: - void EvalFrame(int frame, float *output) { - const unsigned short *dofIndex = GetDOFIndex(); - const float *frameData = GetFrame(frame); - unsigned short numDOFs = mNumDOFs; - - for (int i = 0; i < numDOFs; i++) { - output[*dofIndex++] = *frameData++; - } - } - unsigned short mNumDOFs; // offset 0x4, size 0x2 unsigned short mNumFrames; // offset 0x6, size 0x2 }; From de5c015c72a49f9dc00310a254b83b786eee5a09 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:45:01 +0100 Subject: [PATCH 265/372] 85.50%: index rawlinear eval helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h index 33c6ad57b..0370610b0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h @@ -71,8 +71,8 @@ class RawLinearChannel : public AnimMemoryMap { unsigned short numDOFs = mNumDOFs; for (int i = 0; i < numDOFs; i++) { - float value0 = *frameData0++; - output[*dofIndex++] = t * (*frameData1++ - value0) + value0; + float value0 = frameData0[i]; + output[dofIndex[i]] = t * (frameData1[i] - value0) + value0; } } @@ -83,7 +83,7 @@ class RawLinearChannel : public AnimMemoryMap { unsigned short numDOFs = mNumDOFs; for (int i = 0; i < numDOFs; i++) { - output[*dofIndex++] = *frameData++; + output[dofIndex[i]] = frameData[i]; } } @@ -99,10 +99,10 @@ class RawLinearChannel : public AnimMemoryMap { if (frame < lastFrame) { float t = frameTime - static_cast(frame); - if (t == 0.0f || !interp) { - EvalFrame(frame, output); - } else { + if (t != 0.0f && interp) { EvalInterpFrame(t, frame, frame + 1, output); + } else { + EvalFrame(frame, output); } } else { EvalFrame(lastFrame, output); From 66984c635d161374b050362ed936934ca998e16f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:46:55 +0100 Subject: [PATCH 266/372] 85.54%: keep rawlinear numdofs live Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h index 0370610b0..019799a80 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h @@ -68,9 +68,8 @@ class RawLinearChannel : public AnimMemoryMap { const unsigned short *dofIndex = GetDOFIndex(); const float *frameData0 = GetFrame(frame0); const float *frameData1 = GetFrame(frame1); - unsigned short numDOFs = mNumDOFs; - for (int i = 0; i < numDOFs; i++) { + for (int i = 0; i < mNumDOFs; i++) { float value0 = frameData0[i]; output[dofIndex[i]] = t * (frameData1[i] - value0) + value0; } @@ -80,9 +79,8 @@ class RawLinearChannel : public AnimMemoryMap { void EvalFrame(int frame, float *output) { const unsigned short *dofIndex = GetDOFIndex(); const float *frameData = GetFrame(frame); - unsigned short numDOFs = mNumDOFs; - for (int i = 0; i < numDOFs; i++) { + for (int i = 0; i < mNumDOFs; i++) { output[dofIndex[i]] = frameData[i]; } } From 6466645318bd63d6950c2db7c8612e5f38592d30 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:48:31 +0100 Subject: [PATCH 267/372] 85.69%: inline rawlinear eval flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h index 019799a80..32b3cdc02 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h @@ -94,7 +94,9 @@ class RawLinearChannel : public AnimMemoryMap { } else { int lastFrame = mNumFrames - 1; - if (frame < lastFrame) { + if (frame >= lastFrame) { + EvalFrame(lastFrame, output); + } else { float t = frameTime - static_cast(frame); if (t != 0.0f && interp) { @@ -102,8 +104,6 @@ class RawLinearChannel : public AnimMemoryMap { } else { EvalFrame(frame, output); } - } else { - EvalFrame(lastFrame, output); } } } From 43a2a4d95535f8dc6d960904d730f6757fa0e15d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:50:43 +0100 Subject: [PATCH 268/372] 85.73%: reorder rawpose eval flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index e696e0407..02d5cd55c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -74,12 +74,12 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const } else { int lastFrame = mNumFrames - 1; - if (frame < lastFrame) { + if (frame >= lastFrame) { + EvalFrame(lastFrame, outputPose, boneMask); + } else { float t = frameTime - static_cast(frame); - if (t == 0.0f || !interp) { - EvalFrame(frame, outputPose, boneMask); - } else { + if (t != 0.0f && interp) { int *sig = GetInterpSig(); int *sigEnd = sig + mSigSize; float *data0 = GetFrame(frame); @@ -123,9 +123,9 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const outputPose += 12; } } + } else { + EvalFrame(frame, outputPose, boneMask); } - } else { - EvalFrame(lastFrame, outputPose, boneMask); } } } From 13fc0e3fb7c0d5fbadde7f623ce6acbd032f90ae Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:51:56 +0100 Subject: [PATCH 269/372] 85.74%: tune rawpose interp output walk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index 02d5cd55c..4ae44213e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -88,13 +88,13 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const if (!boneMask) { while (sig < sigEnd) { int numChannels = *sig++; - float *bonePose = outputPose; + float *nextOutputPose = outputPose + 12; - outputPose = bonePose + 12; for (int ichan = 0; ichan < numChannels; ichan++) { reinterpret_cast(*sig++)(t, data0, data1, - bonePose + 4); + outputPose + 4); } + outputPose = nextOutputPose; } } else { for (unsigned int ibone = 0; sig < sigEnd; ibone++) { From e1ea40980a63e678fdc436656ca29750b5f2a68a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:55:02 +0100 Subject: [PATCH 270/372] 85.80%: reorder rawstate decode branches Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index df959e5c8..04963f5eb 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -50,35 +50,30 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { for (int i = 0; i < rawStateChan->GetNumFields(); i++) { unsigned short decodeData = rawStateChan->GetDecodeData()[i]; - unsigned short storedNumBitsInPowersOf2 = decodeData >> 13; + unsigned char storedNumBitsInPowersOf2 = static_cast(decodeData >> 13); unsigned char destNumBytes = static_cast(((decodeData >> 11) & 3) + 1); unsigned char destByteOffset = static_cast(decodeData); - switch (storedNumBitsInPowersOf2) { - case 0: - numBits = (numBits + 1) & 0xFF; - value = (*src >> ((8 - numBits) & 0x1F)) & 1; - break; - case 1: - numBits = (numBits + 2) & 0xFF; - value = (*src >> ((8 - numBits) & 0x1F)) & 3; - break; - case 2: + if (storedNumBitsInPowersOf2 == 2) { numBits = (numBits + 4) & 0xFF; value = (*src >> ((8 - numBits) & 0x3F)) & 0xF; - break; - case 3: - value = *src; - src += 1; - break; - case 4: - value = *reinterpret_cast(src); - src += 2; - break; - case 5: - value = *reinterpret_cast(src); - src += 4; - break; + } else if (storedNumBitsInPowersOf2 > 2) { + if (storedNumBitsInPowersOf2 == 4) { + value = *reinterpret_cast(src); + src += 2; + } else if (storedNumBitsInPowersOf2 < 4) { + value = *src; + src += 1; + } else if (storedNumBitsInPowersOf2 == 5) { + value = *reinterpret_cast(src); + src += 4; + } + } else if (storedNumBitsInPowersOf2 == 0) { + numBits = (numBits + 1) & 0xFF; + value = (*src >> ((8 - numBits) & 0x3F)) & 1; + } else if (storedNumBitsInPowersOf2 == 1) { + numBits = (numBits + 2) & 0xFF; + value = (*src >> ((8 - numBits) & 0x3F)) & 3; } if (numBits > 7) { @@ -86,16 +81,14 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { numBits = 0; } - switch (destNumBytes) { - case 1: - *reinterpret_cast(&dest[destByteOffset]) = static_cast(value); - break; - case 2: - *reinterpret_cast(&dest[destByteOffset]) = static_cast(value); - break; - case 4: - *reinterpret_cast(&dest[destByteOffset]) = value; - break; + if (destNumBytes == 2) { + *reinterpret_cast(&dest[destByteOffset]) = static_cast(value); + } else if (destNumBytes > 2) { + if (destNumBytes == 4) { + *reinterpret_cast(&dest[destByteOffset]) = value; + } + } else if (destNumBytes == 1) { + dest[destByteOffset] = static_cast(value); } } } From db917722e6a795b6af79692cd9ab9bb1eb8a1ca3 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:56:03 +0100 Subject: [PATCH 271/372] 85.82%: spill rawstate decode control bytes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 04963f5eb..890351843 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -50,28 +50,29 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { for (int i = 0; i < rawStateChan->GetNumFields(); i++) { unsigned short decodeData = rawStateChan->GetDecodeData()[i]; - unsigned char storedNumBitsInPowersOf2 = static_cast(decodeData >> 13); - unsigned char destNumBytes = static_cast(((decodeData >> 11) & 3) + 1); - unsigned char destByteOffset = static_cast(decodeData); + unsigned char decodeInfo[3]; + decodeInfo[0] = static_cast(decodeData >> 13); + decodeInfo[1] = static_cast(((decodeData >> 11) & 3) + 1); + decodeInfo[2] = static_cast(decodeData); - if (storedNumBitsInPowersOf2 == 2) { + if (decodeInfo[0] == 2) { numBits = (numBits + 4) & 0xFF; value = (*src >> ((8 - numBits) & 0x3F)) & 0xF; - } else if (storedNumBitsInPowersOf2 > 2) { - if (storedNumBitsInPowersOf2 == 4) { + } else if (decodeInfo[0] > 2) { + if (decodeInfo[0] == 4) { value = *reinterpret_cast(src); src += 2; - } else if (storedNumBitsInPowersOf2 < 4) { + } else if (decodeInfo[0] < 4) { value = *src; src += 1; - } else if (storedNumBitsInPowersOf2 == 5) { + } else if (decodeInfo[0] == 5) { value = *reinterpret_cast(src); src += 4; } - } else if (storedNumBitsInPowersOf2 == 0) { + } else if (decodeInfo[0] == 0) { numBits = (numBits + 1) & 0xFF; value = (*src >> ((8 - numBits) & 0x3F)) & 1; - } else if (storedNumBitsInPowersOf2 == 1) { + } else if (decodeInfo[0] == 1) { numBits = (numBits + 2) & 0xFF; value = (*src >> ((8 - numBits) & 0x3F)) & 3; } @@ -81,14 +82,14 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { numBits = 0; } - if (destNumBytes == 2) { - *reinterpret_cast(&dest[destByteOffset]) = static_cast(value); - } else if (destNumBytes > 2) { - if (destNumBytes == 4) { - *reinterpret_cast(&dest[destByteOffset]) = value; + if (decodeInfo[1] == 2) { + *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); + } else if (decodeInfo[1] > 2) { + if (decodeInfo[1] == 4) { + *reinterpret_cast(&dest[decodeInfo[2]]) = value; } - } else if (destNumBytes == 1) { - dest[destByteOffset] = static_cast(value); + } else if (decodeInfo[1] == 1) { + dest[decodeInfo[2]] = static_cast(value); } } } From 737c60bb04d0232c9259ff2bfdb1e11f4af9605a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:57:26 +0100 Subject: [PATCH 272/372] 85.85%: invert rawstate decode writeback flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 890351843..e10238a48 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -82,14 +82,16 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { numBits = 0; } - if (decodeInfo[1] == 2) { - *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); - } else if (decodeInfo[1] > 2) { - if (decodeInfo[1] == 4) { - *reinterpret_cast(&dest[decodeInfo[2]]) = value; + if (decodeInfo[1] != 2) { + if (decodeInfo[1] > 2) { + if (decodeInfo[1] == 4) { + *reinterpret_cast(&dest[decodeInfo[2]]) = value; + } + } else if (decodeInfo[1] == 1) { + dest[decodeInfo[2]] = static_cast(value); } - } else if (decodeInfo[1] == 1) { - dest[decodeInfo[2]] = static_cast(value); + } else { + *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); } } } From b795031f3403905941d68958b53a30f8868f6b54 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 11:59:05 +0100 Subject: [PATCH 273/372] 85.93%: lay out rawstate decode blocks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 106 +++++++++++++----- 1 file changed, 77 insertions(+), 29 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index e10238a48..739ec603b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -56,43 +56,91 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { decodeInfo[2] = static_cast(decodeData); if (decodeInfo[0] == 2) { - numBits = (numBits + 4) & 0xFF; - value = (*src >> ((8 - numBits) & 0x3F)) & 0xF; - } else if (decodeInfo[0] > 2) { - if (decodeInfo[0] == 4) { - value = *reinterpret_cast(src); - src += 2; - } else if (decodeInfo[0] < 4) { - value = *src; - src += 1; - } else if (decodeInfo[0] == 5) { - value = *reinterpret_cast(src); - src += 4; - } - } else if (decodeInfo[0] == 0) { - numBits = (numBits + 1) & 0xFF; - value = (*src >> ((8 - numBits) & 0x3F)) & 1; - } else if (decodeInfo[0] == 1) { - numBits = (numBits + 2) & 0xFF; - value = (*src >> ((8 - numBits) & 0x3F)) & 3; + goto Decode4Bits; + } + if (decodeInfo[0] > 2) { + goto DecodeWide; + } + if (decodeInfo[0] == 0) { + goto Decode1Bit; + } + if (decodeInfo[0] == 1) { + goto Decode2Bits; + } + goto DecodeDone; + + DecodeWide: + if (decodeInfo[0] == 4) { + goto DecodeU16; } + if (decodeInfo[0] < 4) { + goto DecodeU8; + } + if (decodeInfo[0] != 5) { + goto DecodeDone; + } + value = *reinterpret_cast(src); + src += 4; + goto DecodeDone; + + DecodeU16: + value = *reinterpret_cast(src); + src += 2; + goto DecodeDone; + + DecodeU8: + value = *src; + src += 1; + goto DecodeDone; + + Decode4Bits: + numBits = (numBits + 4) & 0xFF; + value = (*src >> ((8 - numBits) & 0x3F)) & 0xF; + goto DecodeDone; + + Decode2Bits: + numBits = (numBits + 2) & 0xFF; + value = (*src >> ((8 - numBits) & 0x3F)) & 3; + goto DecodeDone; + + Decode1Bit: + numBits = (numBits + 1) & 0xFF; + value = (*src >> ((8 - numBits) & 0x3F)) & 1; + + DecodeDone: if (numBits > 7) { src += 1; numBits = 0; } - if (decodeInfo[1] != 2) { - if (decodeInfo[1] > 2) { - if (decodeInfo[1] == 4) { - *reinterpret_cast(&dest[decodeInfo[2]]) = value; - } - } else if (decodeInfo[1] == 1) { - dest[decodeInfo[2]] = static_cast(value); - } - } else { - *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); + if (decodeInfo[1] == 2) { + goto Store2Bytes; + } + if (decodeInfo[1] > 2) { + goto StoreWide; + } + if (decodeInfo[1] == 1) { + goto Store1Byte; } + goto StoreDone; + + StoreWide: + if (decodeInfo[1] != 4) { + goto StoreDone; + } + *reinterpret_cast(&dest[decodeInfo[2]]) = value; + goto StoreDone; + + Store1Byte: + dest[decodeInfo[2]] = static_cast(value); + goto StoreDone; + + Store2Bytes: + *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); + + StoreDone: + ; } } From 3a58981b6fcdce38576a84f5c537480834c92920 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:00:24 +0100 Subject: [PATCH 274/372] 85.96%: reorder rawstate decode stores Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 739ec603b..382637cf7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -95,17 +95,17 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { Decode4Bits: numBits = (numBits + 4) & 0xFF; - value = (*src >> ((8 - numBits) & 0x3F)) & 0xF; + value = (*src >> (8 - numBits)) & 0xF; goto DecodeDone; Decode2Bits: numBits = (numBits + 2) & 0xFF; - value = (*src >> ((8 - numBits) & 0x3F)) & 3; + value = (*src >> (8 - numBits)) & 3; goto DecodeDone; Decode1Bit: numBits = (numBits + 1) & 0xFF; - value = (*src >> ((8 - numBits) & 0x3F)) & 1; + value = (*src >> (8 - numBits)) & 1; DecodeDone: @@ -132,12 +132,12 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { *reinterpret_cast(&dest[decodeInfo[2]]) = value; goto StoreDone; - Store1Byte: - dest[decodeInfo[2]] = static_cast(value); - goto StoreDone; - Store2Bytes: *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); + goto StoreDone; + + Store1Byte: + dest[decodeInfo[2]] = static_cast(value); StoreDone: ; From ebc55cec55b208e0563894d44b3f6f68db7972dc Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:01:35 +0100 Subject: [PATCH 275/372] 85.97%: sign rawstate decode control bytes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 382637cf7..487d0dc96 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -50,21 +50,18 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { for (int i = 0; i < rawStateChan->GetNumFields(); i++) { unsigned short decodeData = rawStateChan->GetDecodeData()[i]; - unsigned char decodeInfo[3]; - decodeInfo[0] = static_cast(decodeData >> 13); - decodeInfo[1] = static_cast(((decodeData >> 11) & 3) + 1); - decodeInfo[2] = static_cast(decodeData); + char decodeInfo[3]; + decodeInfo[0] = static_cast(decodeData >> 13); + decodeInfo[1] = static_cast(((decodeData >> 11) & 3) + 1); + decodeInfo[2] = static_cast(decodeData); if (decodeInfo[0] == 2) { goto Decode4Bits; - } - if (decodeInfo[0] > 2) { + } else if (decodeInfo[0] > 2) { goto DecodeWide; - } - if (decodeInfo[0] == 0) { + } else if (decodeInfo[0] == 0) { goto Decode1Bit; - } - if (decodeInfo[0] == 1) { + } else if (decodeInfo[0] == 1) { goto Decode2Bits; } goto DecodeDone; @@ -116,11 +113,9 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { if (decodeInfo[1] == 2) { goto Store2Bytes; - } - if (decodeInfo[1] > 2) { + } else if (decodeInfo[1] > 2) { goto StoreWide; - } - if (decodeInfo[1] == 1) { + } else if (decodeInfo[1] == 1) { goto Store1Byte; } goto StoreDone; @@ -129,15 +124,16 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { if (decodeInfo[1] != 4) { goto StoreDone; } - *reinterpret_cast(&dest[decodeInfo[2]]) = value; + *reinterpret_cast(&dest[static_cast(decodeInfo[2])]) = value; goto StoreDone; Store2Bytes: - *reinterpret_cast(&dest[decodeInfo[2]]) = static_cast(value); + *reinterpret_cast(&dest[static_cast(decodeInfo[2])]) = + static_cast(value); goto StoreDone; Store1Byte: - dest[decodeInfo[2]] = static_cast(value); + dest[static_cast(decodeInfo[2])] = static_cast(value); StoreDone: ; From 613f738ed3d653d8a23916a91887fd0092de2ed8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:03:40 +0100 Subject: [PATCH 276/372] 85.97%: trim rawstate decode bit shifts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 487d0dc96..750892a06 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -111,17 +111,17 @@ void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { numBits = 0; } - if (decodeInfo[1] == 2) { + if (static_cast(decodeInfo[1]) == 2) { goto Store2Bytes; - } else if (decodeInfo[1] > 2) { + } else if (static_cast(decodeInfo[1]) > 2) { goto StoreWide; - } else if (decodeInfo[1] == 1) { + } else if (static_cast(decodeInfo[1]) == 1) { goto Store1Byte; } goto StoreDone; StoreWide: - if (decodeInfo[1] != 4) { + if (static_cast(decodeInfo[1]) != 4) { goto StoreDone; } *reinterpret_cast(&dest[static_cast(decodeInfo[2])]) = value; From a09f214f9c75d71d7f73bff660c3fac872845b69 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:05:33 +0100 Subject: [PATCH 277/372] 86.03%: reshape rawstate eval flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 750892a06..d7c6a8de5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -144,8 +144,10 @@ bool FnRawStateChan::EvalState(float time, State *s) { RawStateChan *rawStateChan = reinterpret_cast(mpAnim); unsigned char numFields = rawStateChan->GetNumFields(); unsigned char keySize = rawStateChan->GetKeySize(); + unsigned short numKeys; unsigned char *keyData; int keyIdx = mKeyIdx; + unsigned char *currKey; if ((numFields & 1) == 0) { keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; @@ -153,41 +155,72 @@ bool FnRawStateChan::EvalState(float time, State *s) { keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; } - if (time < *reinterpret_cast(&keyData[keyIdx * keySize])) { - keyIdx--; + if (*reinterpret_cast(&keyData[keyIdx * keySize]) <= time) { + numKeys = rawStateChan->GetNumKeys(); - while (keyIdx > -1) { - unsigned char *currKey = &keyData[keyIdx * keySize]; + if (keyIdx < numKeys) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; - if (*reinterpret_cast(currKey) <= time) { - Decode(currKey + sizeof(float), reinterpret_cast(s)); - mKeyIdx = keyIdx; - return true; - } + do { + currKey = keyData + 2; + if ((numFields & 1) != 0) { + currKey = keyData; + } + currKey += keyIdx * keySize; - keyIdx--; + if (time < *reinterpret_cast(currKey + keySize)) { + goto KeyFound; + } + + keyIdx++; + } while (keyIdx < numKeys); } - Decode(keyData + sizeof(float), reinterpret_cast(s)); - mKeyIdx = 0; + if ((numFields & 1) == 0) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; + } else { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + } + + Decode(keyData + keySize * (numKeys - 1) + sizeof(float), reinterpret_cast(s)); + mKeyIdx = numKeys - 1; } else { - unsigned short numKeys = rawStateChan->GetNumKeys(); + keyIdx--; - for (; keyIdx < numKeys; keyIdx++) { - unsigned char *currKey = &keyData[keyIdx * keySize]; + if (keyIdx > -1) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; - if (time < *reinterpret_cast(currKey + keySize)) { - Decode(currKey + sizeof(float), reinterpret_cast(s)); - mKeyIdx = keyIdx; - return true; - } + do { + currKey = keyData + 2; + if ((numFields & 1) != 0) { + currKey = keyData; + } + currKey += keyIdx * keySize; + + if (*reinterpret_cast(currKey) <= time) { + goto KeyFound; + } + + keyIdx--; + } while (keyIdx > -1); } - Decode(keyData + keySize * (numKeys - 1) + sizeof(float), reinterpret_cast(s)); - mKeyIdx = numKeys - 1; + if ((numFields & 1) == 0) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; + } else { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + } + + Decode(keyData + sizeof(float), reinterpret_cast(s)); + mKeyIdx = 0; } return true; + +KeyFound: + Decode(currKey + sizeof(float), reinterpret_cast(s)); + mKeyIdx = keyIdx; + return true; } bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &resultTime) { From e10e11a8c8fde4b437d308c784472f3736a2a1b0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:07:58 +0100 Subject: [PATCH 278/372] 86.05%: inline rawstate key walk Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index d7c6a8de5..d8baa611f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -225,20 +225,44 @@ bool FnRawStateChan::EvalState(float time, State *s) { bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &resultTime) { const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); - const unsigned char *keyData = GetRawStateKeyData(rawStateChan); - unsigned char keySize = rawStateChan->GetKeySize(); unsigned char stateBuffer[84]; + int keyIdx = 0; + unsigned short numKeys = rawStateChan->GetNumKeys(); + + if (numKeys != 0) { + unsigned char numFields = rawStateChan->GetNumFields(); + + while (true) { + int keyDataOffset; + const float *currKey; + float keyTime; + + if ((numFields & 1) == 0) { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 12; + } else { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 10; + } - for (int keyIdx = 0; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { - const unsigned char *currKey = &keyData[keyIdx * keySize]; - float keyTime = *reinterpret_cast(currKey); + currKey = reinterpret_cast( + reinterpret_cast(rawStateChan) + keyDataOffset + keyIdx * rawStateChan->GetKeySize()); + keyTime = *currKey; - if (startTime < keyTime) { - Decode(const_cast(currKey + sizeof(float)), stateBuffer); - if (test.Pass(reinterpret_cast(stateBuffer))) { - resultTime = keyTime; - return true; + if (startTime < keyTime) { + Decode(const_cast(reinterpret_cast(currKey + 1)), stateBuffer); + if (test.Pass(reinterpret_cast(stateBuffer))) { + resultTime = keyTime; + return true; + } + + numKeys = rawStateChan->GetNumKeys(); } + + keyIdx++; + if (numKeys <= keyIdx) { + break; + } + + numFields = rawStateChan->GetNumFields(); } } From 67a69409380011a5773a6a0aa0e54da1f5f6bd3b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:12:40 +0100 Subject: [PATCH 279/372] 86.07%: count rawstate key search directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index d8baa611f..229977aa3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -226,43 +226,30 @@ bool FnRawStateChan::EvalState(float time, State *s) { bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &resultTime) { const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); unsigned char stateBuffer[84]; - int keyIdx = 0; - unsigned short numKeys = rawStateChan->GetNumKeys(); + int keyIdx; - if (numKeys != 0) { + for (keyIdx = 0; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { + int keyDataOffset; + const float *currKey; + float keyTime; unsigned char numFields = rawStateChan->GetNumFields(); - while (true) { - int keyDataOffset; - const float *currKey; - float keyTime; - - if ((numFields & 1) == 0) { - keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 12; - } else { - keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 10; - } - - currKey = reinterpret_cast( - reinterpret_cast(rawStateChan) + keyDataOffset + keyIdx * rawStateChan->GetKeySize()); - keyTime = *currKey; - - if (startTime < keyTime) { - Decode(const_cast(reinterpret_cast(currKey + 1)), stateBuffer); - if (test.Pass(reinterpret_cast(stateBuffer))) { - resultTime = keyTime; - return true; - } + if ((numFields & 1) == 0) { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 12; + } else { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 10; + } - numKeys = rawStateChan->GetNumKeys(); - } + currKey = reinterpret_cast( + reinterpret_cast(rawStateChan) + keyDataOffset + keyIdx * rawStateChan->GetKeySize()); + keyTime = *currKey; - keyIdx++; - if (numKeys <= keyIdx) { - break; + if (startTime < keyTime) { + Decode(const_cast(reinterpret_cast(currKey + 1)), stateBuffer); + if (test.Pass(reinterpret_cast(stateBuffer))) { + resultTime = keyTime; + return true; } - - numFields = rawStateChan->GetNumFields(); } } From d33a62a5545cc60acb586af1d314519ac2a45aa7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Tue, 24 Mar 2026 12:20:42 +0100 Subject: [PATCH 280/372] 86.07%: tune rawstate findtime parity checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index 229977aa3..f2c644bdf 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -234,17 +234,17 @@ bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &res float keyTime; unsigned char numFields = rawStateChan->GetNumFields(); - if ((numFields & 1) == 0) { - keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 12; - } else { + if (numFields & 1) { keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 10; + } else { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 12; } currKey = reinterpret_cast( reinterpret_cast(rawStateChan) + keyDataOffset + keyIdx * rawStateChan->GetKeySize()); keyTime = *currKey; - if (startTime < keyTime) { + if (keyTime > startTime) { Decode(const_cast(reinterpret_cast(currKey + 1)), stateBuffer); if (test.Pass(reinterpret_cast(stateBuffer))) { resultTime = keyTime; From 314785058c913106fa62b52155c56fe28b921cb1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 11:40:05 +0100 Subject: [PATCH 281/372] 86.16%: improve FnRunBlender::SetWeight Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 01804af49..2b2847463 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -1,5 +1,6 @@ #include "FnRunBlender.h" +#include "AnimUtil.h" #include "FnPoseBlender.h" #include "MemoryPoolManager.h" #include "PhaseChan.h" @@ -104,33 +105,34 @@ void FnRunBlender::Eval(float prevTime, float currTime, float *pose) { } void FnRunBlender::SetWeight(float w) { - int idx = static_cast(w); + int i = FloatToInt(w); + float prevFreq; - if (idx < 0) { - idx = 0; + if (i < 0) { + i = 0; } - if (idx >= mNumAnims - 1) { - idx = mNumAnims - 2; + if (i >= mNumAnims - 1) { + i = mNumAnims - 2; } - mWeight = w - static_cast(idx); + mWeight = w - static_cast(i); - if (idx == mIdx) { - float prevFreq = mFreq; + if (i == mIdx) { + prevFreq = mFreq; mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; return; } - if (idx == mIdx + 1) { + if (i == mIdx + 1) { MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); mFnAnims[0] = mFnAnims[1]; - mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx + 1]))); - } else if (idx == mIdx - 1) { + mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i + 1]))); + } else if (i == mIdx - 1) { MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); mFnAnims[1] = mFnAnims[0]; - mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx]))); + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i]))); } else { if (mFnAnims[0]) { MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); @@ -139,19 +141,19 @@ void FnRunBlender::SetWeight(float w) { MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); } - mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx]))); - mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[idx + 1]))); + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i]))); + mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i + 1]))); } if (mVels) { - if (idx == mIdx + 1) { + if (i == mIdx + 1) { MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); mFnVelAnims[0] = mFnVelAnims[1]; - mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx + 1]))); - } else if (idx == mIdx - 1) { + mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i + 1]))); + } else if (i == mIdx - 1) { MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); mFnVelAnims[1] = mFnVelAnims[0]; - mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx]))); + mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i]))); } else { if (mFnVelAnims[0]) { MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); @@ -160,29 +162,27 @@ void FnRunBlender::SetWeight(float w) { MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); } - mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx]))); - mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[idx + 1]))); + mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i]))); + mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i + 1]))); } } - const PhaseChan *phase0 = mPhases[idx]; - const PhaseChan *phase1 = mPhases[idx + 1]; - float prevFreq = mFreq; + prevFreq = mFreq; - if (!phase0->StartWithRight()) { - mAlignFrame[0] = static_cast(phase0->mStartTime + phase0->mCycles[0]); + if (!mPhases[i]->StartWithRight()) { + mAlignFrame[0] = static_cast(mPhases[i]->mStartTime + mPhases[i]->mCycles[0]); } else { - mAlignFrame[0] = static_cast(phase0->mStartTime); + mAlignFrame[0] = static_cast(mPhases[i]->mStartTime); } - mAlignFrame[1] = static_cast(phase1->mStartTime); - if (!phase1->StartWithRight()) { - mAlignFrame[1] += static_cast(phase1->mCycles[0]); + mAlignFrame[1] = static_cast(mPhases[i + 1]->mStartTime); + if (!mPhases[i + 1]->StartWithRight()) { + mAlignFrame[1] += static_cast(mPhases[i + 1]->mCycles[0]); } - mIdx = idx; - mCycles[0] = static_cast(phase0->mCycles[0] + phase0->mCycles[1]) * 0.5f; - mCycles[1] = static_cast(phase1->mCycles[0] + phase1->mCycles[1]) * 0.5f; + mIdx = i; + mCycles[0] = static_cast(mPhases[i]->mCycles[0] + mPhases[i]->mCycles[1]) * 0.5f; + mCycles[1] = static_cast(mPhases[i + 1]->mCycles[0] + mPhases[i + 1]->mCycles[1]) * 0.5f; mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; } From 3fe6e0700e77bb6a7166f3b8370cfecece0af59f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 12:04:58 +0100 Subject: [PATCH 282/372] 86.17%: improve Transform::BuildSQT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 608010efc..30016e262 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -114,28 +114,36 @@ void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTra } void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float qz, float qw, float tx, float ty, float tz) { - float *mat = m.GetElements(); - float qy2 = qy + qy; - float qz2 = qz + qz; - float qx2qx = qx * (qx + qx); - float qw2qx = qw * (qx + qx); - - mat[12] = tx; - mat[13] = ty; - mat[15] = 1.0f; - mat[11] = 0.0f; - mat[3] = 0.0f; - mat[7] = 0.0f; - mat[14] = tz; - mat[2] = sx * (qw * qz2 - qw * qy2); - mat[6] = sy * (qy * qz2 + qw2qx); - mat[10] = sz * (1.0f - (qx2qx + qy * qy2)); - mat[0] = sx * (1.0f - (qy * qy2 + qz * qz2)); - mat[4] = sy * (qw * qy2 - qz * qz2); - mat[8] = sz * (qw * qz2 + qz * qy2); - mat[1] = sx * (qw * qy2 + qz * qz2); - mat[5] = sy * (1.0f - (qx2qx + qz * qz2)); - mat[9] = sz * (qy * qz2 - qw2qx); + float s = 1.0f; + float xs = qx + qx; + float ys = qy + qy; + float zs = qz + qz; + float wx = qw * xs; + float wy = qw * ys; + float wz = qw * zs; + float xx = qx * xs; + float xy = qx * ys; + float xz = qx * zs; + float yy = qy * ys; + float yz = qy * zs; + float zz = qz * zs; + + m[3].x = tx; + m[3].y = ty; + m[3].w = s; + m[2].w = 0.0f; + m[0].w = 0.0f; + m[1].w = 0.0f; + m[3].z = tz; + m[0].z = sx * (xz - wy); + m[1].z = sy * (yz + wx); + m[2].z = sz * (s - (xx + yy)); + m[0].x = sx * (s - (yy + zz)); + m[1].x = sy * (xy - wz); + m[2].x = sz * (xz + wy); + m[0].y = sx * (xy + wz); + m[1].y = sy * (s - (xx + zz)); + m[2].y = sz * (yz - wx); } }; // namespace EAGL4 From e96c333e1f137ad8e0e630ead74260bde972c6e7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 12:07:36 +0100 Subject: [PATCH 283/372] 86.19%: improve Transform::ExtractQuatTrans Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 30016e262..16c388954 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -92,25 +92,24 @@ void Transform::PostMult(const Transform &second) { } void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTrans) const { - const float *mat = m.GetElements(); UMath::Matrix3 m3; - m3[0][0] = mat[0]; - m3[0][1] = mat[1]; - m3[0][2] = mat[2]; - m3[1][0] = mat[4]; - m3[1][1] = mat[5]; - m3[1][2] = mat[6]; - m3[2][0] = mat[8]; - m3[2][1] = mat[9]; - m3[2][2] = mat[10]; + m3.v0.x = m.v0.x; + m3.v0.y = m.v0.y; + m3.v0.z = m.v0.z; + m3.v1.x = m.v1.x; + m3.v1.y = m.v1.y; + m3.v1.z = m.v1.z; + m3.v2.x = m.v2.x; + m3.v2.y = m.v2.y; + m3.v2.z = m.v2.z; EAGL4m3toquat(&m3, retQuat); - retTrans->x = mat[12]; - retTrans->y = mat[13]; - retTrans->z = mat[14]; - retTrans->w = mat[15]; + retTrans->x = m.v3.x; + retTrans->y = m.v3.y; + retTrans->z = m.v3.z; + retTrans->w = m.v3.w; } void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float qz, float qw, float tx, float ty, float tz) { @@ -128,22 +127,22 @@ void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float float yz = qy * zs; float zz = qz * zs; - m[3].x = tx; - m[3].y = ty; - m[3].w = s; - m[2].w = 0.0f; - m[0].w = 0.0f; - m[1].w = 0.0f; - m[3].z = tz; - m[0].z = sx * (xz - wy); - m[1].z = sy * (yz + wx); - m[2].z = sz * (s - (xx + yy)); - m[0].x = sx * (s - (yy + zz)); - m[1].x = sy * (xy - wz); - m[2].x = sz * (xz + wy); - m[0].y = sx * (xy + wz); - m[1].y = sy * (s - (xx + zz)); - m[2].y = sz * (yz - wx); + m.v3.x = tx; + m.v3.y = ty; + m.v3.w = s; + m.v2.w = 0.0f; + m.v0.w = 0.0f; + m.v1.w = 0.0f; + m.v3.z = tz; + m.v0.z = sx * (xz - wy); + m.v1.z = sy * (yz + wx); + m.v2.z = sz * (s - (xx + yy)); + m.v0.x = sx * (s - (yy + zz)); + m.v1.x = sy * (xy - wz); + m.v2.x = sz * (xz + wy); + m.v0.y = sx * (xy + wz); + m.v1.y = sy * (s - (xx + zz)); + m.v2.y = sz * (yz - wx); } }; // namespace EAGL4 From 2bd31706bc6f026d845b16cf6f91c8b1ecc21608 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 12:39:01 +0100 Subject: [PATCH 284/372] 86.37%: improve BlendFacing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 2b2847463..4922f9b14 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -15,6 +15,41 @@ float length(float *v) { namespace EAGL4Anim { +static const UMath::Vector4 kFacingAxis = {0.0f, 1.0f, 0.0f, 1.0f}; + +static inline void QuatTransformPoint(const UMath::Vector4 &q, const UMath::Vector4 &p, UMath::Vector4 &result) { + float zz; + float yz; + float yy; + float xz; + float xy; + float xx; + float wz; + float wy; + float wx; + float zs; + float ys; + float xs; + float s; + + s = 2.0f; + xs = q.x * s; + ys = q.y * s; + zs = q.z * s; + wx = q.w * xs; + wy = q.w * ys; + wz = q.w * zs; + xx = q.x * xs; + xy = q.x * ys; + xz = q.x * zs; + yy = q.y * ys; + yz = q.y * zs; + zz = q.z * zs; + result.x = p.x * (p.w - (yy + zz)) + p.y * (xy - wz) + p.z * (xz + wy); + result.y = p.x * (xy + wz) + p.y * (p.w - (xx + zz)) + p.z * (yz - wx); + result.z = p.x * (xz - wy) + p.y * (yz + wx) + p.z * (p.w - (xx + yy)); +} + FnRunBlender::FnRunBlender() : mFreq(1.0f), // mOffset(0.0f), // @@ -259,31 +294,34 @@ bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { return false; } - ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); - float *pose = reinterpret_cast(scratch.GetBuffer()); + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); UMath::Vector4 q0; UMath::Vector4 q; + UMath::Vector4 xAxis; + UMath::Vector4 xAxis1; - if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, pose, 0)) { + if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, buffer, 0)) { return false; } - q0 = *reinterpret_cast(&pose[4]); + q0 = *reinterpret_cast(&buffer[4]); - if (mWeight == 0.0f) { - q = q0; - } else { - mSkeleton->GetStillPose(pose, 0); - if (!mFnAnims[1] || !mFnAnims[1]->EvalSQT(t1, pose, 0)) { + if (mWeight != 0.0f) { + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1] || !mFnAnims[1]->EvalSQT(t1, buffer, 0)) { return false; } - UMath::Vector4 q1 = *reinterpret_cast(&pose[4]); + UMath::Vector4 q1 = *reinterpret_cast(&buffer[4]); FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + } else { + q = q0; } - f[0] = 2.0f * (q.w * q.z - q.x * q.y); - f[1] = 2.0f * (q.y * q.z + q.x * q.w); + xAxis = kFacingAxis; + QuatTransformPoint(q, xAxis, xAxis1); + f[0] = xAxis1.x; + f[1] = xAxis1.z; printf("Facing: %g %g\n", f[0], f[1]); return true; } From 38b653845f6a35ac3a2bde19e6958c2ce0a1c7e1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 12:44:09 +0100 Subject: [PATCH 285/372] 86.38%: improve FnStatelessQ::EvalSQTMask Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 3e5fd2eb5..ceb4fb273 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -204,15 +204,19 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool } StatelessQ *statelessQ = reinterpret_cast(mpAnim); - sqt += 4; + float *q = sqt + 4; unsigned short *dataBuf = statelessQ->GetData(); unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); + unsigned short *prevData; + unsigned short *nextData; unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; - unsigned short index; + int index; + int numConsts; if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { - unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); + int nextKey = floorKey + 1; + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); for (int ibone = 0; ibone < nBones; ibone++) { unsigned char boneIdx = boneIdxs[ibone]; @@ -220,23 +224,23 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool if (boneMask->GetBone(boneIdx)) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - unsigned short *currFrame = &frameData[ibone * 4]; - unsigned short *nextFrame = &nextFrameData[ibone * 4]; - - prevQ.x = UncompressStatelessQValue(*currFrame++); - prevQ.y = UncompressStatelessQValue(*currFrame++); - prevQ.z = UncompressStatelessQValue(*currFrame++); - prevQ.w = UncompressStatelessQValue(*currFrame); - nextQ.x = UncompressStatelessQValue(*nextFrame++); - nextQ.y = UncompressStatelessQValue(*nextFrame++); - nextQ.z = UncompressStatelessQValue(*nextFrame++); - nextQ.w = UncompressStatelessQValue(*nextFrame); + prevData = &frameData[ibone * 4]; + nextData = &nextFrameData[ibone * 4]; + + prevQ.x = UncompressStatelessQValue(*prevData++); + prevQ.y = UncompressStatelessQValue(*prevData++); + prevQ.z = UncompressStatelessQValue(*prevData++); + prevQ.w = UncompressStatelessQValue(*prevData); + nextQ.x = UncompressStatelessQValue(*nextData++); + nextQ.y = UncompressStatelessQValue(*nextData++); + nextQ.z = UncompressStatelessQValue(*nextData++); + nextQ.w = UncompressStatelessQValue(*nextData); index = boneIdx * 12; - sqt[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; - sqt[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; - sqt[index + 2] = scale * (nextQ.z - prevQ.z) + prevQ.z; - sqt[index + 3] = scale * (nextQ.w - prevQ.w) + prevQ.w; + q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; + q[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; + q[index + 2] = scale * (nextQ.z - prevQ.z) + prevQ.z; + q[index + 3] = scale * (nextQ.w - prevQ.w) + prevQ.w; } } } else { @@ -244,20 +248,20 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned char boneIdx = boneIdxs[ibone]; if (boneMask->GetBone(boneIdx)) { - unsigned short *currFrame = &frameData[ibone * 4]; + prevData = &frameData[ibone * 4]; index = boneIdx * 12; - sqt[index + 0] = UncompressStatelessQValue(*currFrame++); - sqt[index + 1] = UncompressStatelessQValue(*currFrame++); - sqt[index + 2] = UncompressStatelessQValue(*currFrame++); - sqt[index + 3] = UncompressStatelessQValue(*currFrame); + q[index + 0] = UncompressStatelessQValue(*prevData++); + q[index + 1] = UncompressStatelessQValue(*prevData++); + q[index + 2] = UncompressStatelessQValue(*prevData++); + q[index + 3] = UncompressStatelessQValue(*prevData); } } } - if (statelessQ->mNumConstBones != 0) { - int numConsts = statelessQ->mNumConstBones; + numConsts = statelessQ->mNumConstBones; + if (numConsts != 0) { unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); unsigned short *constBuf = statelessQ->GetConstData(dataBuf); @@ -269,10 +273,10 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool index = boneIdx * 12; - sqt[index + 0] = UncompressStatelessQValue(*currFrame++); - sqt[index + 1] = UncompressStatelessQValue(*currFrame++); - sqt[index + 2] = UncompressStatelessQValue(*currFrame++); - sqt[index + 3] = UncompressStatelessQValue(*currFrame); + q[index + 0] = UncompressStatelessQValue(*currFrame++); + q[index + 1] = UncompressStatelessQValue(*currFrame++); + q[index + 2] = UncompressStatelessQValue(*currFrame++); + q[index + 3] = UncompressStatelessQValue(*currFrame); } } } From fc901afca36bc850f2f3201496ec1fe6663bd15c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 12:51:28 +0100 Subject: [PATCH 286/372] 86.49%: improve DynamicLoader::Resolve Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index a62d2f8ae..e508ae072 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -196,12 +196,11 @@ void DynamicLoader::Resolve() { break; } - char *name = &h->strtab[sym->st_name]; HashPointer *hp; for (hp = hashhead; hp; hp = hp->next) { if (hp != h) { - void *addr = dlsym(hp, name); + void *addr = dlsym(hp, &h->strtab[sym->st_name]); if (addr) { sym->st_shndx = 1; @@ -219,7 +218,7 @@ void DynamicLoader::Resolve() { if (h->pSearchFunction) { bool valid = false; - void *addr = h->pSearchFunction(name, valid); + void *addr = h->pSearchFunction(&h->strtab[sym->st_name], valid); if (valid) { sym->st_shndx = 1; @@ -232,7 +231,7 @@ void DynamicLoader::Resolve() { { bool valid = false; - void *addr = gSymbolPool.Search(name, valid); + void *addr = gSymbolPool.Search(&h->strtab[sym->st_name], valid); if (valid) { sym->st_shndx = 1; @@ -243,23 +242,22 @@ void DynamicLoader::Resolve() { } } - const char *type = name + strlen(name); + Symbol s; - if (type[1] == 0x7F) { - type += 2; + s.name = &h->strtab[sym->st_name]; + s.type = s.name + strlen(s.name); + if (s.type[1] == 0x7F) { + s.type += 2; } - if (strncmp(gRuntimeAllocType, name, strlen(gRuntimeAllocType)) == 0) { - Symbol s; + if (strncmp(gRuntimeAllocType, s.name, strlen(gRuntimeAllocType)) == 0) { const char *stripped_name; RuntimeAllocConstructor c; - s.name = name; - s.type = type; stripped_name = s.name + strlen(gRuntimeAllocType); c = gRuntimeAllocConsPool.FindConstructor(s.type); if (c) { - RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(type); + RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(s.type); int auxData = 0; bool bCallDestructor = false; void *addr = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, s.name); @@ -283,14 +281,14 @@ void DynamicLoader::Resolve() { bool found = false; for (j = 0; j < numUnresolved; j++) { - if (name == unresolvedList[j]) { + if (s.name == unresolvedList[j]) { found = true; break; } } if (!found) { - unresolvedList[numUnresolved++] = name; + unresolvedList[numUnresolved++] = const_cast(s.name); } j = iIndex; } From 972b77b0942f5906fb29cb2aa2a994ae532b21a7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 12:56:10 +0100 Subject: [PATCH 287/372] 86.489%: refine DynamicLoader::Resolve Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index e508ae072..c5e58729f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -242,55 +242,57 @@ void DynamicLoader::Resolve() { } } - Symbol s; - - s.name = &h->strtab[sym->st_name]; - s.type = s.name + strlen(s.name); - if (s.type[1] == 0x7F) { - s.type += 2; - } - if (strncmp(gRuntimeAllocType, s.name, strlen(gRuntimeAllocType)) == 0) { - const char *stripped_name; - RuntimeAllocConstructor c; - - stripped_name = s.name + strlen(gRuntimeAllocType); - c = gRuntimeAllocConsPool.FindConstructor(s.type); - - if (c) { - RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(s.type); - int auxData = 0; - bool bCallDestructor = false; - void *addr = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, s.name); - - if (bCallDestructor && addr) { - RuntimeAllocDestructorEntry *de = new RuntimeAllocDestructorEntry(d, addr, auxData); - de->next = RuntimeAllocDestructors; - RuntimeAllocDestructors = de; - } + { + Symbol s; - sym->st_shndx = 1; - sym->st_other = 5; - baseaddr = reinterpret_cast(sheader[1].sh_voffset); - sym->st_value = reinterpret_cast(addr) - baseaddr; - break; + s.name = &h->strtab[sym->st_name]; + s.type = s.name + strlen(s.name); + if (s.type[1] == 0x7F) { + s.type += 2; } - } + if (strncmp(gRuntimeAllocType, s.name, strlen(gRuntimeAllocType)) == 0) { + const char *stripped_name; + RuntimeAllocConstructor c; - unresolvedSymbolError = true; - if (numUnresolved < MAX_UNRESOLVED_ERRORS) { - bool found = false; + stripped_name = s.name + strlen(gRuntimeAllocType); + c = gRuntimeAllocConsPool.FindConstructor(s.type); - for (j = 0; j < numUnresolved; j++) { - if (s.name == unresolvedList[j]) { - found = true; + if (c) { + RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(s.type); + int auxData = 0; + bool bCallDestructor = false; + s.data = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, s.name); + + if (bCallDestructor && s.data) { + RuntimeAllocDestructorEntry *de = new RuntimeAllocDestructorEntry(d, s.data, auxData); + de->next = RuntimeAllocDestructors; + RuntimeAllocDestructors = de; + } + + sym->st_shndx = 1; + sym->st_other = 5; + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(s.data) - baseaddr; break; } } - if (!found) { - unresolvedList[numUnresolved++] = const_cast(s.name); + unresolvedSymbolError = true; + if (numUnresolved < MAX_UNRESOLVED_ERRORS) { + bool found = false; + + for (j = 0; j < numUnresolved; j++) { + if (s.name == unresolvedList[j]) { + found = true; + break; + } + } + + if (!found) { + unresolvedList[numUnresolved++] = const_cast(s.name); + } + j = iIndex; } - j = iIndex; } break; } From f48a20fd2e1b7832a39f4c67bc81a20516247df9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 13:09:33 +0100 Subject: [PATCH 288/372] 86.74%: improve FnDeltaQ helper usage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h | 25 +++- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 127 ++++----------------- 2 files changed, 46 insertions(+), 106 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h index 366a160b9..300f7e939 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h @@ -41,7 +41,6 @@ struct DeltaQDelta { q.x = minRangef.mMin.x + minRangef.mRange.x * mX; q.y = minRangef.mMin.y + minRangef.mRange.y * mY; q.z = minRangef.mMin.z + minRangef.mRange.z * mZ; - DeltaQRecoverW(mW, q); } unsigned char mX : 7; // offset 0x0, size 0x1 @@ -91,6 +90,30 @@ inline void DeltaQRecoverW(int signBit, UMath::Vector4 &q) { } } +inline void FastPolarizedQuatBlend(float t, const UMath::Vector4 &q0, const UMath::Vector4 &q1, UMath::Vector4 &result) { + float s; + UMath::Vector4 temp; + + if (q0.x * q1.x + q0.y * q1.y + q0.z * q1.z + q0.w * q1.w > 0.0f) { + temp = q1; + } else { + temp.x = -q1.x; + temp.y = -q1.y; + temp.z = -q1.z; + temp.w = -q1.w; + } + + result.x = t * (temp.x - q0.x) + q0.x; + result.y = t * (temp.y - q0.y) + q0.y; + result.z = t * (temp.z - q0.z) + q0.z; + result.w = t * (temp.w - q0.w) + q0.w; + s = 1.0f / FastSqrt(result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w); + result.x *= s; + result.y *= s; + result.z *= s; + result.w *= s; +} + // total size: 0x14 struct DeltaQ : public AnimMemoryMap { static int ComputeSize(int numBones, int numConst, int numFrames, int binLen, bool useKeyFrames) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 7565fac3d..c81abf0fc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -144,9 +144,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - mPrevQs[ibone].x = floorPhys[ibone].mX * 6.1037019e-5f - 1.0f; - mPrevQs[ibone].y = floorPhys[ibone].mY * 3.0518044e-5f - 1.0f; - mPrevQs[ibone].z = floorPhys[ibone].mZ * 3.0518044e-5f - 1.0f; + floorPhys[ibone].UnQuantize(mPrevQs[ibone]); } prevDeltaIdx = 0; } else { @@ -162,9 +160,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; - deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; - deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; + floorDelta->UnQuantize(minRangef, deltaf); mPrevQs[ibone].x += deltaf.x; mPrevQs[ibone].y += deltaf.y; mPrevQs[ibone].z += deltaf.z; @@ -180,9 +176,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; - deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; - deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; + floorDelta->UnQuantize(minRangef, deltaf); mPrevQs[ibone].x -= deltaf.x; mPrevQs[ibone].y -= deltaf.y; mPrevQs[ibone].z -= deltaf.z; @@ -236,51 +230,26 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (ceilBinIdx != floorBinIdx) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { UMath::Vector4 ceilq; - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *ceilQ = reinterpret_cast(&ceilq); - float *out = GetOutputQuat(sqt, boneIdxs[ibone]); + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); ceilPhys[ibone].UnQuantize(ceilq); - if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { - out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; - out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; - out[2] = scale * (ceilQ[2] - prevQ[2]) + prevQ[2]; - out[3] = scale * (ceilQ[3] - prevQ[3]) + prevQ[3]; - } else { - out[0] = prevQ[0] - scale * (ceilQ[0] + prevQ[0]); - out[1] = prevQ[1] - scale * (ceilQ[1] + prevQ[1]); - out[2] = prevQ[2] - scale * (ceilQ[2] + prevQ[2]); - out[3] = prevQ[3] - scale * (ceilQ[3] + prevQ[3]); - } - - { - float s = 1.0f / FastSqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]); - - out[0] *= s; - out[1] *= s; - out[2] *= s; - out[3] *= s; - } + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); } } else { DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { - UMath::Vector4 deltaf; UMath::Vector4 ceilq; + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - deltaf.x = minRangef.mMin.x + minRangef.mRange.x * ceilDelta->mX; - deltaf.y = minRangef.mMin.y + minRangef.mRange.y * ceilDelta->mY; - deltaf.z = minRangef.mMin.z + minRangef.mRange.z * ceilDelta->mZ; - ceilq.x = mPrevQs[ibone].x + deltaf.x; - ceilq.y = mPrevQs[ibone].y + deltaf.y; - ceilq.z = mPrevQs[ibone].z + deltaf.z; + ceilDelta->UnQuantize(minRangef, ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; DeltaQRecoverW(ceilDelta->mW, ceilq); - - FastQuatBlendF4(scale, reinterpret_cast(&mPrevQs[ibone]), reinterpret_cast(&ceilq), - GetOutputQuat(sqt, boneIdxs[ibone])); + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); ceilDelta++; } } @@ -360,9 +329,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - mPrevQs[ibone].x = floorPhys[ibone].mX * 6.1037019e-5f - 1.0f; - mPrevQs[ibone].y = floorPhys[ibone].mY * 3.0518044e-5f - 1.0f; - mPrevQs[ibone].z = floorPhys[ibone].mZ * 3.0518044e-5f - 1.0f; + floorPhys[ibone].UnQuantize(mPrevQs[ibone]); } } prevDeltaIdx = 0; @@ -380,9 +347,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; - deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; - deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; + floorDelta->UnQuantize(minRangef, deltaf); mPrevQs[ibone].x += deltaf.x; mPrevQs[ibone].y += deltaf.y; mPrevQs[ibone].z += deltaf.z; @@ -400,9 +365,7 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - deltaf.x = minRangef.mMin.x + minRangef.mRange.x * floorDelta->mX; - deltaf.y = minRangef.mMin.y + minRangef.mRange.y * floorDelta->mY; - deltaf.z = minRangef.mMin.z + minRangef.mRange.z * floorDelta->mZ; + floorDelta->UnQuantize(minRangef, deltaf); mPrevQs[ibone].x -= deltaf.x; mPrevQs[ibone].y -= deltaf.y; mPrevQs[ibone].z -= deltaf.z; @@ -462,31 +425,10 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 ceilq; - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *ceilQ = reinterpret_cast(&ceilq); - float *out = GetOutputQuat(sqt, boneIdxs[ibone]); + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); ceilPhys[ibone].UnQuantize(ceilq); - if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { - out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; - out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; - out[2] = scale * (ceilQ[2] - prevQ[2]) + prevQ[2]; - out[3] = scale * (ceilQ[3] - prevQ[3]) + prevQ[3]; - } else { - out[0] = prevQ[0] - scale * (ceilQ[0] + prevQ[0]); - out[1] = prevQ[1] - scale * (ceilQ[1] + prevQ[1]); - out[2] = prevQ[2] - scale * (ceilQ[2] + prevQ[2]); - out[3] = prevQ[3] - scale * (ceilQ[3] + prevQ[3]); - } - - { - float s = 1.0f / FastSqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]); - - out[0] *= s; - out[1] *= s; - out[2] *= s; - out[3] *= s; - } + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); } } } else { @@ -494,42 +436,17 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { - UMath::Vector4 deltaf; UMath::Vector4 ceilq; - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - float *ceilQ = reinterpret_cast(&ceilq); - float *out = GetOutputQuat(sqt, boneIdxs[ibone]); + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); DeltaQMinRangef minRangef; mMinRanges[ibone].UnQuantize(minRangef); - deltaf.x = minRangef.mMin.x + minRangef.mRange.x * ceilDelta->mX; - deltaf.y = minRangef.mMin.y + minRangef.mRange.y * ceilDelta->mY; - deltaf.z = minRangef.mMin.z + minRangef.mRange.z * ceilDelta->mZ; - ceilq.x = mPrevQs[ibone].x + deltaf.x; - ceilq.y = mPrevQs[ibone].y + deltaf.y; - ceilq.z = mPrevQs[ibone].z + deltaf.z; + ceilDelta->UnQuantize(minRangef, ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; DeltaQRecoverW(ceilDelta->mW, ceilq); - - if (prevQ[0] * ceilQ[0] + prevQ[1] * ceilQ[1] + prevQ[2] * ceilQ[2] + prevQ[3] * ceilQ[3] > 0.0f) { - out[0] = scale * (ceilQ[0] - prevQ[0]) + prevQ[0]; - out[1] = scale * (ceilQ[1] - prevQ[1]) + prevQ[1]; - out[2] = scale * (ceilQ[2] - prevQ[2]) + prevQ[2]; - out[3] = scale * (ceilQ[3] - prevQ[3]) + prevQ[3]; - } else { - out[0] = prevQ[0] - scale * (ceilQ[0] + prevQ[0]); - out[1] = prevQ[1] - scale * (ceilQ[1] + prevQ[1]); - out[2] = prevQ[2] - scale * (ceilQ[2] + prevQ[2]); - out[3] = prevQ[3] - scale * (ceilQ[3] + prevQ[3]); - } - - { - float s = 1.0f / FastSqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]); - - out[0] *= s; - out[1] *= s; - out[2] *= s; - out[3] *= s; - } + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); } ceilDelta++; } From c60511dca92777910ad32e8492b98d94f843e202 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 13:26:06 +0100 Subject: [PATCH 289/372] 86.775%: improve FnDeltaSingleQ init helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h | 35 ++++++++++++ .../Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp | 56 ++++++------------- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h b/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h index f7f01f8e8..323cdc982 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h +++ b/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h @@ -19,6 +19,41 @@ inline float FastSqrt(float x) { return sqrtf(x); } +inline void EulToQuat(const float *eulData, float *quatData) { + float ss; + float sc; + float cs; + float cc; + float sh; + float sj; + float si; + float ch; + float cj; + float ci; + float th; + float tj; + float ti; + + ti = eulData[0] * 0.5f; + tj = eulData[1] * 0.5f; + th = eulData[2] * 0.5f; + ci = cosf(ti); + cj = cosf(tj); + ch = cosf(th); + si = sinf(ti); + sj = sinf(tj); + sh = sinf(th); + cc = ci * cj; + cs = ci * sj; + sc = si * cj; + ss = si * sj; + + quatData[0] = sc * ch - cs * sh; + quatData[1] = cs * ch + sc * sh; + quatData[2] = cc * sh - ss * ch; + quatData[3] = cc * ch + ss * sh; +} + inline intptr_t AlignSize16(intptr_t size) { return (size + 15) & ~15; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index 2f2e6b9f6..d2bd951af 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -85,27 +85,6 @@ static inline void NormalizeSingleQQuat(UMath::Vector4 &q) { q.w *= s; } -static inline void SingleQEulToQuat(const float *eulData, float *quatData) { - float ti = eulData[0] * kSingleQHalf; - float tj = eulData[1] * kSingleQHalf; - float th = eulData[2] * kSingleQHalf; - float ci = cosf(ti); - float cj = cosf(tj); - float ch = cosf(th); - float si = sinf(ti); - float sj = sinf(tj); - float sh = sinf(th); - float cc = ci * cj; - float cs = ci * sj; - float sc = si * cj; - float ss = si * sj; - - quatData[0] = sc * ch - cs * sh; - quatData[1] = cs * ch + sc * sh; - quatData[2] = cc * sh - ss * ch; - quatData[3] = cc * ch + ss * sh; -} - static inline void SingleQQuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { float awby = a.w * b.y; float axbw = a.x * b.w; @@ -217,13 +196,13 @@ void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { } inline void FnDeltaSingleQ::InitBuffersAsRequired() { - float eul[3]; - int ibone; DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); DeltaSingleQMinRange *minRanges; + int ibone; + float eul[3]; + DeltaSingleQMinRangef *pMinRangef; - minRanges = reinterpret_cast(&deltaQ[1]); - mBins = &reinterpret_cast(minRanges)[deltaQ->mNumBones * sizeof(DeltaSingleQMinRange)]; + deltaQ->GetArrays(minRanges, mBins); mBinSize = deltaQ->GetBinSize(); mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); mPrevQs = reinterpret_cast(mPrevQBlock); @@ -234,36 +213,37 @@ inline void FnDeltaSingleQ::InitBuffersAsRequired() { for (ibone = 0; ibone < deltaQ->mNumBones; ibone++) { DeltaSingleQMinRangef minRangef; - mMinRanges[ibone].UnQuantize(minRangef); + pMinRangef = &minRangef; + mMinRanges[ibone].UnQuantize(*pMinRangef); - if (minRangef.mIndex == 0) { + if (pMinRangef->mIndex == 0) { mPreMultQs[ibone].x = kSingleQFloatZero; mPreMultQs[ibone].y = kSingleQFloatZero; mPreMultQs[ibone].z = kSingleQFloatZero; mPreMultQs[ibone].w = kSingleQFloatOne; eul[0] = kSingleQFloatZero; - eul[1] = minRangef.mConst0; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); - } else if (minRangef.mIndex == 1) { - eul[0] = minRangef.mConst0; + eul[1] = pMinRangef->mConst0; + eul[2] = pMinRangef->mConst1; + EulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (pMinRangef->mIndex == 1) { + eul[0] = pMinRangef->mConst0; eul[1] = kSingleQFloatZero; eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + EulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); eul[0] = kSingleQFloatZero; eul[1] = kSingleQFloatZero; - eul[2] = minRangef.mConst1; - SingleQEulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + eul[2] = pMinRangef->mConst1; + EulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); } else { mPostMultQs[ibone].x = kSingleQFloatZero; mPostMultQs[ibone].y = kSingleQFloatZero; mPostMultQs[ibone].z = kSingleQFloatZero; mPostMultQs[ibone].w = kSingleQFloatOne; - eul[0] = minRangef.mConst0; - eul[1] = minRangef.mConst1; + eul[0] = pMinRangef->mConst0; + eul[1] = pMinRangef->mConst1; eul[2] = kSingleQFloatZero; - SingleQEulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + EulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); } } } From 53e2c8e6fe69141399ce06f90c530f8a1306c189 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 13:28:56 +0100 Subject: [PATCH 290/372] 86.806%: improve DeltaSingleQ header helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h index 1f7d495e5..fcd2babe8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h @@ -23,8 +23,8 @@ struct DeltaSingleQMinRangef { // total size: 0xE struct DeltaSingleQMinRange { void GetAlignment(float &c0, float &c1) { - c0 = mConst0 * 9.5875265e-5f - 3.1415927f; - c1 = mConst1 * 9.5875265e-5f - 3.1415927f; + c0 = mConst0 * 9.5875265e-5f + -3.1415927f; + c1 = mConst1 * 9.5875265e-5f + -3.1415927f; } void UnQuantize(DeltaSingleQMinRangef &minRangef) { @@ -119,8 +119,10 @@ struct DeltaSingleQ : public AnimMemoryMap { } void GetArrays(DeltaSingleQMinRange *&minRanges, unsigned char *&binStart) { - minRanges = GetMinRange(); - binStart = &reinterpret_cast(minRanges)[mNumBones * sizeof(DeltaSingleQMinRange)]; + unsigned char *memBytes = reinterpret_cast(&this[1]); + + minRanges = reinterpret_cast(memBytes); + binStart = &memBytes[mNumBones * sizeof(DeltaSingleQMinRange)]; } int GetBinSize() const { From ca4233ddb999de457b5cf1c1b2cbad72a8df15e7 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 14:27:09 +0100 Subject: [PATCH 291/372] 86.83%: improve masked FnDeltaQ reverse guard Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index c81abf0fc..50703a86c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -325,8 +325,8 @@ bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sq int prevDeltaIdx; unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaQPhysical *floorPhys = GetPhysical(binData); - if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || - (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { if (boneMask->GetBone(boneIdxs[ibone])) { floorPhys[ibone].UnQuantize(mPrevQs[ibone]); From 5c3c461927f071a99a1ffbae31b29884aab841f0 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 14:47:36 +0100 Subject: [PATCH 292/372] 86.84%: improve FnDeltaQ fast-path reuse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 50703a86c..72077cf03 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -140,15 +140,19 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaQPhysical *floorPhys = GetPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; - bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + bool preventReverse = false; - if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + if (floorKey < mPrevKey) { + preventReverse = !IsReverseDeltaSumEnabled(); + } + + if (mPrevKey != -1 && floorBinIdx == prevBinIdx && floorDeltaIdx != 0 && !preventReverse) { + prevDeltaIdx = mPrevKey & binLenModMask; + } else { for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { floorPhys[ibone].UnQuantize(mPrevQs[ibone]); } prevDeltaIdx = 0; - } else { - prevDeltaIdx = mPrevKey & binLenModMask; } if (prevDeltaIdx < floorDeltaIdx) { From d77c7fdc21368bff3df3c54035b264eb4ca0bc0a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 14:54:57 +0100 Subject: [PATCH 293/372] 86.85%: improve FnDeltaQFast reuse path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 6c47e8fba..f03c7ce0a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -445,6 +445,7 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) } else { int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; int prevDeltaIdx = mPrevKey; + bool preventReverse = false; if (mPrevKey == static_cast(floorKey + 1)) { for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { @@ -454,26 +455,29 @@ bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) mNextKey = mPrevKey; } - if (prevDeltaIdx == -1 || floorBinIdx != (mPrevKey >> deltaQ->mBinLengthPower) || floorDeltaIdx == 0 || - (floorKey < mPrevKey && !IsReverseDeltaSumEnabled())) { - for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { - float *prevQ = reinterpret_cast(&mPrevQs[ibone]); - DeltaQFastPhysical *physical = reinterpret_cast(binData + ibone * 6); - - prevQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; - prevQ[3] = - static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * - kQFastPhysicalScale12 - - kQFastPhysicalBias12; - } - - prevDeltaIdx = 0; - } else { - prevDeltaIdx &= static_cast(binLengthMask); + if (floorKey < mPrevKey) { + preventReverse = !IsReverseDeltaSumEnabled(); + } + + if (prevDeltaIdx != -1 && floorBinIdx == (mPrevKey >> deltaQ->mBinLengthPower) && floorDeltaIdx != 0 && !preventReverse) { + prevDeltaIdx &= static_cast(binLengthMask); + } else { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + DeltaQFastPhysical *physical = reinterpret_cast(binData + ibone * 6); + + prevQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; } + prevDeltaIdx = 0; + } + if (prevDeltaIdx < static_cast(floorDeltaIdx)) { AddDelta(reinterpret_cast(binData), deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); } else if (static_cast(floorDeltaIdx) < prevDeltaIdx) { From 4010e76252292955d0252d5d27bc0e24ced75a3a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 15:04:06 +0100 Subject: [PATCH 294/372] 86.86%: improve FnStatelessQ mask locals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index ceb4fb273..76b81bfc6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -138,7 +138,7 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); unsigned char *boneIdxs = statelessQ->mBoneIdxs; int nBones = statelessQ->mNumBones; - unsigned short index; + int index; if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); @@ -219,9 +219,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); for (int ibone = 0; ibone < nBones; ibone++) { - unsigned char boneIdx = boneIdxs[ibone]; - - if (boneMask->GetBone(boneIdx)) { + if (boneMask->GetBone(boneIdxs[ibone])) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; prevData = &frameData[ibone * 4]; @@ -235,7 +233,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool nextQ.y = UncompressStatelessQValue(*nextData++); nextQ.z = UncompressStatelessQValue(*nextData++); nextQ.w = UncompressStatelessQValue(*nextData); - index = boneIdx * 12; + index = boneIdxs[ibone] * 12; q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; q[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; @@ -245,12 +243,10 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool } } else { for (int ibone = 0; ibone < nBones; ibone++) { - unsigned char boneIdx = boneIdxs[ibone]; - - if (boneMask->GetBone(boneIdx)) { + if (boneMask->GetBone(boneIdxs[ibone])) { prevData = &frameData[ibone * 4]; - index = boneIdx * 12; + index = boneIdxs[ibone] * 12; q[index + 0] = UncompressStatelessQValue(*prevData++); q[index + 1] = UncompressStatelessQValue(*prevData++); @@ -266,17 +262,15 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool unsigned short *constBuf = statelessQ->GetConstData(dataBuf); for (int ibone = 0; ibone < numConsts; ibone++) { - unsigned char boneIdx = constIdxs[ibone]; - - if (boneMask->GetBone(boneIdx)) { - unsigned short *currFrame = &constBuf[ibone * 4]; + if (boneMask->GetBone(constIdxs[ibone])) { + prevData = &constBuf[ibone * 4]; - index = boneIdx * 12; + index = constIdxs[ibone] * 12; - q[index + 0] = UncompressStatelessQValue(*currFrame++); - q[index + 1] = UncompressStatelessQValue(*currFrame++); - q[index + 2] = UncompressStatelessQValue(*currFrame++); - q[index + 3] = UncompressStatelessQValue(*currFrame); + q[index + 0] = UncompressStatelessQValue(*prevData++); + q[index + 1] = UncompressStatelessQValue(*prevData++); + q[index + 2] = UncompressStatelessQValue(*prevData++); + q[index + 3] = UncompressStatelessQValue(*prevData); } } } From ba07133d3565e415c47beef1ed93a743953a8ce6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 15:11:48 +0100 Subject: [PATCH 295/372] 86.87%: use LoadStatelessQ in masked stateless slerp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 76b81bfc6..09d92a092 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -225,14 +225,8 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool prevData = &frameData[ibone * 4]; nextData = &nextFrameData[ibone * 4]; - prevQ.x = UncompressStatelessQValue(*prevData++); - prevQ.y = UncompressStatelessQValue(*prevData++); - prevQ.z = UncompressStatelessQValue(*prevData++); - prevQ.w = UncompressStatelessQValue(*prevData); - nextQ.x = UncompressStatelessQValue(*nextData++); - nextQ.y = UncompressStatelessQValue(*nextData++); - nextQ.z = UncompressStatelessQValue(*nextData++); - nextQ.w = UncompressStatelessQValue(*nextData); + LoadStatelessQ(prevData, prevQ); + LoadStatelessQ(nextData, nextQ); index = boneIdxs[ibone] * 12; q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; From 945ea7fb6c949e6211e8f46c23ad73a595fb70e6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 15:17:50 +0100 Subject: [PATCH 296/372] 86.90%: improve DynamicLoader Resolve locals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index c5e58729f..dcd9ee355 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -217,7 +217,7 @@ void DynamicLoader::Resolve() { } if (h->pSearchFunction) { - bool valid = false; + bool valid; void *addr = h->pSearchFunction(&h->strtab[sym->st_name], valid); if (valid) { @@ -230,7 +230,7 @@ void DynamicLoader::Resolve() { } { - bool valid = false; + bool valid; void *addr = gSymbolPool.Search(&h->strtab[sym->st_name], valid); if (valid) { @@ -259,7 +259,7 @@ void DynamicLoader::Resolve() { if (c) { RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(s.type); - int auxData = 0; + int auxData; bool bCallDestructor = false; s.data = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, s.name); @@ -277,7 +277,9 @@ void DynamicLoader::Resolve() { } } - unresolvedSymbolError = true; + if (!unresolvedSymbolError) { + unresolvedSymbolError = true; + } if (numUnresolved < MAX_UNRESOLVED_ERRORS) { bool found = false; From 50c911c10fb204aa34bfde3b7fe53bc44eb1fa87 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 15:22:17 +0100 Subject: [PATCH 297/372] 86.93%: improve DynamicLoader Resolve symbol paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index dcd9ee355..8325df792 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -246,9 +246,11 @@ void DynamicLoader::Resolve() { Symbol s; s.name = &h->strtab[sym->st_name]; - s.type = s.name + strlen(s.name); - if (s.type[1] == 0x7F) { - s.type += 2; + s.type = s.name + strlen(s.name) + 1; + if (s.type[0] == 0x7F) { + s.type++; + } else { + s.type--; } if (strncmp(gRuntimeAllocType, s.name, strlen(gRuntimeAllocType)) == 0) { const char *stripped_name; @@ -284,14 +286,14 @@ void DynamicLoader::Resolve() { bool found = false; for (j = 0; j < numUnresolved; j++) { - if (s.name == unresolvedList[j]) { + if (&h->strtab[sym->st_name] == unresolvedList[j]) { found = true; break; } } if (!found) { - unresolvedList[numUnresolved++] = const_cast(s.name); + unresolvedList[numUnresolved++] = &h->strtab[sym->st_name]; } j = iIndex; } From bb403c47144a96ba0cb96df6ed95d07166a9567f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 15:44:29 +0100 Subject: [PATCH 298/372] 86.95%: inline PoseBlender end root transform Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 16c388954..308b57bbd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -218,8 +218,22 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask if (result) { if (mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { - EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, sqtBuffer, mAlignRootBoneIdx); + float *sqt1 = &sqtBuffer[mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; } return true; } From 5c01200c9cb548d91e58a9c8c0cc7be9c54a66f9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:00:26 +0100 Subject: [PATCH 299/372] 86.98%: inline PoseBlender end root transforms Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 308b57bbd..dca2731f9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -322,8 +322,22 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], outputPose); if (mAlignRootBoneIdx >= 0) { - EAGL4::Transform rootTransform; - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, outputPose, mAlignRootBoneIdx); + float *sqt1 = &outputPose[mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; } return; From 206bbc80fd94d0850fcffd5f32bc0e4673969a47 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:06:19 +0100 Subject: [PATCH 300/372] 86.99%: inline PoseBlender loop root transform Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index dca2731f9..052e7d956 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -359,9 +359,22 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos if (mAlignRootBoneIdx >= 0) { for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { if (boneIdx == mAlignRootBoneIdx) { - EAGL4::Transform rootTransform; + float *sqt1 = &mPose[1][mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], mAlignRootBoneIdx); + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; BlendRootTranslation(w, mPose[0], mPose[1], outputPose, mAlignRootBoneIdx); } From f9a82d35a87549690779441ef2e8875e5403dae6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:08:14 +0100 Subject: [PATCH 301/372] 87.01%: inline PoseBlender masked loop root transform Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 052e7d956..4c2b21bcd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -254,16 +254,30 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask return false; } - if (mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { - if (!boneMask) { - if (mAlignRootBoneIdx >= 0) { - for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { - if (boneIdx == mAlignRootBoneIdx) { - EAGL4::Transform rootTransform; - - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], mAlignRootBoneIdx); - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); - } + if (mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { + if (!boneMask) { + if (mAlignRootBoneIdx >= 0) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { + if (boneIdx == mAlignRootBoneIdx) { + float *sqt1 = &mPose[1][mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], + sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); + } int poseIdx = boneIdx * 12; From 6b1ccef741465490d386a021a4e910936664bd4d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:09:19 +0100 Subject: [PATCH 302/372] 87.02%: inline PoseBlender masked root transforms Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 4c2b21bcd..3d5d350d2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -296,9 +296,23 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { if (boneMask->GetBone(boneIdx)) { if (boneIdx == mAlignRootBoneIdx) { - EAGL4::Transform rootTransform; - - ApplyAlignedRootTransform(rootTransform, mAlignMatrix, mPose[1], mAlignRootBoneIdx); + float *sqt1 = &mPose[1][mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], + sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); } From 8d5eff4be11178b4bdd7daa39c5950451c86d1be Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:12:34 +0100 Subject: [PATCH 303/372] 87.03%: inline PoseBlender root translation blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 3d5d350d2..3ed8a1ffd 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -2,6 +2,12 @@ #include "AnimUtil.h" +static inline void LinearBlendF3(float w, const float *d0, const float *d1, float *out) { + out[0] = d0[0] + w * (d1[0] - d0[0]); + out[1] = d0[1] + w * (d1[1] - d0[1]); + out[2] = d0[2] + w * (d1[2] - d0[2]); +} + namespace EAGL4 { @@ -403,7 +409,8 @@ void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPos sqt1[8] = trans.x; sqt1[9] = trans.y; sqt1[10] = trans.z; - BlendRootTranslation(w, mPose[0], mPose[1], outputPose, mAlignRootBoneIdx); + ::LinearBlendF3(w, &mPose[0][mAlignRootBoneIdx * 12 + 8], &mPose[1][mAlignRootBoneIdx * 12 + 8], + &outputPose[mAlignRootBoneIdx * 12 + 8]); } int poseIdx = boneIdx * 12; From a119a60006a8d38b4704cfa7f3bcc3de05166127 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:14:03 +0100 Subject: [PATCH 304/372] 87.03%: inline PoseBlender unmasked translation blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 3ed8a1ffd..7de5a0687 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -282,7 +282,8 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask sqt1[8] = trans.x; sqt1[9] = trans.y; sqt1[10] = trans.z; - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); + ::LinearBlendF3(w, &mPose[0][mAlignRootBoneIdx * 12 + 8], &mPose[1][mAlignRootBoneIdx * 12 + 8], + &sqtBuffer[mAlignRootBoneIdx * 12 + 8]); } int poseIdx = boneIdx * 12; From 5d080b6e8807c3eb6f06b8d9b809f72ff0a489a1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:15:24 +0100 Subject: [PATCH 305/372] 87.04%: inline PoseBlender masked translation blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 7de5a0687..0d0a7e7d9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -320,7 +320,8 @@ bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask sqt1[8] = trans.x; sqt1[9] = trans.y; sqt1[10] = trans.z; - BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, mAlignRootBoneIdx); + ::LinearBlendF3(w, &mPose[0][mAlignRootBoneIdx * 12 + 8], &mPose[1][mAlignRootBoneIdx * 12 + 8], + &sqtBuffer[mAlignRootBoneIdx * 12 + 8]); } int poseIdx = boneIdx * 12; From e1036da6cbeac4b40c88dc4b8e408faf3a30a909 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 16:28:52 +0100 Subject: [PATCH 306/372] 87.05%: delay DeltaQ anim pointer load Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 72077cf03..6240673a1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -91,11 +91,11 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { if (boneMask) { return EvalSQTMasked(currTime, boneMask, sqt); } - DeltaQ *deltaQ = reinterpret_cast(mpAnim); if (!mBins) { InitBuffersAsRequired(); } + DeltaQ *deltaQ = reinterpret_cast(mpAnim); int floorTime = FloatToInt(currTime); int floorKey; From 0abc5ef579015146af31ac50cc041b839ed32945 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:03:00 +0100 Subject: [PATCH 307/372] 87.17%: restore anim delete operators Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h | 5 ++++- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h | 5 ++++- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h | 5 ++++- src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h | 5 ++++- src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h index 14e6405b9..8d307a487 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h @@ -7,6 +7,7 @@ #include "FnAnimMemoryMap.h" #include "PosePalette.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class FnPoseAnim : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h index 54ffbaa02..e6187b4b7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h @@ -7,6 +7,7 @@ #include "BoneMask.h" #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class FnStatelessF3 : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h index 8bb95746a..3fcf67b3a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h @@ -7,6 +7,7 @@ #include "BoneMask.h" #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" #include @@ -19,7 +20,9 @@ class FnStatelessQ : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h index d0b9c1974..07afb13f9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h @@ -6,6 +6,7 @@ #endif #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -29,7 +30,9 @@ struct PhaseChan : public AnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h index 57be55aac..4598f71ff 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h @@ -7,6 +7,7 @@ #include "AnimMemoryMap.h" #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class RawStateChan : public AnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} From d72713b8f302e64454d2543b7eb1223cc908e8fe Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:04:16 +0100 Subject: [PATCH 308/372] 87.23%: restore more anim delete operators Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h | 4 +++- src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h index 07afb13f9..fbf21be65 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h @@ -132,7 +132,9 @@ class FnPhaseChan : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h index 4598f71ff..ddc00e838 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h @@ -89,7 +89,9 @@ class FnRawStateChan : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} From deadc9cf7f55293ca1dad9c10891ad9406f1d609 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:09:00 +0100 Subject: [PATCH 309/372] 87.26%: use implicit anim destructors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp | 2 -- src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h | 3 --- src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h | 3 --- 3 files changed, 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index a5b2af945..535875a49 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -12,8 +12,6 @@ FnPoseAnim::FnPoseAnim() { mPrevKey = 0; } -FnPoseAnim::~FnPoseAnim() {} - void FnPoseAnim::SetAnimMemoryMap(AnimMemoryMap *anim) { mpAnim = anim; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h index 8d307a487..7df7ee9b3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h @@ -32,9 +32,6 @@ class FnPoseAnim : public FnAnimMemoryMap { return ptr; } - // Overrides: FnAnimSuper - ~FnPoseAnim() override; - static void PatchVtbl(FnPoseAnim *poseAnim) {} FnPoseAnim(); diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h index fbf21be65..47673f650 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h @@ -152,9 +152,6 @@ class FnPhaseChan : public FnAnimMemoryMap { mType = AnimTypeId::ANIM_PHASE; } - // Overrides: FnAnimSuper - ~FnPhaseChan() override {} - // Overrides: FnAnim bool GetLength(float &timeLength) const override; From b0e72c8ec211e7f87a51fa3b1e33443ca88c1009 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:19:15 +0100 Subject: [PATCH 310/372] 87.26%: return StatelessF3 masked fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index 1f535a11b..d25459e1e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -170,7 +170,7 @@ bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask } } } else { - EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + return EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); } return true; From a4edd97d27720e87f25f57ff5563a2f487fe3c24 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:21:12 +0100 Subject: [PATCH 311/372] 87.27%: return StatelessF3 fast masked fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index d25459e1e..a4ff7fb2e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -304,7 +304,7 @@ bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *bone } } } else { - EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + return EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); } return true; From 768f2cd36cfdccce50c3329d82b9b69d06f5bfa1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:30:39 +0100 Subject: [PATCH 312/372] 87.28%: tighten DynamicLoader release lifetime Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 8325df792..305766767 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -471,9 +471,8 @@ DynamicLoader::~DynamicLoader() { } void DynamicLoader::Release() { - if (handle) { - HashPointer *h = reinterpret_cast(handle); - + HashPointer *h = reinterpret_cast(handle); + if (h) { if (h->prev) { h->prev->next = h->next; } else { @@ -493,7 +492,8 @@ void DynamicLoader::Release() { } EAGL4Internal::EAGL4Free(h, sizeof(HashPointer)); - handle = nullptr; + h = nullptr; + handle = h; } mIsResolved = false; } From 1cfe2c3f70a233cb672d3ac330f862c8991d9e78 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:40:43 +0100 Subject: [PATCH 313/372] 87.30%: restore DeltaQ zero-bone exits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 5 +++++ src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 6240673a1..ce845c3a9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -96,6 +96,10 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { InitBuffersAsRequired(); } DeltaQ *deltaQ = reinterpret_cast(mpAnim); + + if (!deltaQ->mNumBones) { + return true; + } int floorTime = FloatToInt(currTime); int floorKey; @@ -140,6 +144,7 @@ bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { unsigned char *binData = &mBins[floorBinIdx * mBinSize]; DeltaQPhysical *floorPhys = GetPhysical(binData); unsigned char *boneIdxs = deltaQ->mBoneIdxs; + bool preventReverse = false; if (floorKey < mPrevKey) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h index 7612f418a..5ce31f3f2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h @@ -84,8 +84,6 @@ class FnDeltaQ : public FnAnimMemoryMap { mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); mPrevQs = reinterpret_cast(mPrevQBlock); mMinRanges = minRanges; - } else { - mMinRanges = minRanges; } } From 5629c278cdbe31a595adf325eed876ffed2c31e8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:49:30 +0100 Subject: [PATCH 314/372] 87.35%: simplify StatelessQ key search Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 09d92a092..077e329ab 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -82,27 +82,16 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) if (floorTime < statelessQ->mTimes[0]) { floorKey = 0; } else { - int timeIndex = 0; + int timeIndex = mPrevKey != 0 ? mPrevKey - 1 : 0; - if (mPrevKey != 0) { - timeIndex = mPrevKey - 1; - } if (statelessQ->mTimes[timeIndex] <= floorTime) { - if (timeIndex < statelessQ->mNumKeys - 2) { - while (statelessQ->mTimes[timeIndex + 1] <= floorTime) { - timeIndex++; - if (timeIndex >= statelessQ->mNumKeys - 2) { - break; - } - } + while (timeIndex < statelessQ->mNumKeys - 2 && statelessQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; } - } else if (timeIndex > 0) { - do { + } else { + while (timeIndex > 0 && statelessQ->mTimes[timeIndex] > floorTime) { timeIndex--; - if (timeIndex < 1) { - break; - } - } while (statelessQ->mTimes[timeIndex] > floorTime); + } } floorKey = timeIndex + 1; From c26ffb073adcd15e490d8e55a3e1e66aab99cbca Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:54:13 +0100 Subject: [PATCH 315/372] 87.41%: invert FindMatchTime delta branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 4922f9b14..16c37e203 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -509,10 +509,10 @@ bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) cons float deltaAngle = phase.mAngle - prevAngle; if (0.0f <= deltaAngle * input.mDAngle) { - if (deltaAngle == 0.0f) { - time = static_cast(i - 1) + 0.5f; - } else { + if (deltaAngle != 0.0f) { time = (input.mAngle - prevAngle) / deltaAngle + static_cast(i - 1); + } else { + time = static_cast(i - 1) + 0.5f; } return true; } From a5d94a179060a8a2369f2acb82819c3e25ef4204 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 17:56:15 +0100 Subject: [PATCH 316/372] 87.46%: cache FindMatchTime inputs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 16c37e203..68619e50a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -484,17 +484,20 @@ void FnRunBlender::AlignVel(float *vel) const { bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { PhaseValue phase; + float inputAngle = input.mAngle; + float inputDAngle = input.mDAngle; + float searchLength = input.mSearchLength; float cycleLength = mWeight * (mCycles[1] - mCycles[0]) + mCycles[0]; int searchCount = static_cast(cycleLength + cycleLength); phase.mAngle = 0.0f; - if (0.0f < input.mSearchLength && input.mSearchLength < static_cast(searchCount)) { - searchCount = static_cast(input.mSearchLength) + 1; + if (0.0f < searchLength && searchLength < static_cast(searchCount)) { + searchCount = static_cast(searchLength) + 1; } const_cast(this)->EvalPhase(0.0f, phase); - float bestDelta = input.mAngle - phase.mAngle; + float bestDelta = inputAngle - phase.mAngle; if (bestDelta < 0.0f) { bestDelta = -bestDelta; } @@ -505,12 +508,12 @@ bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) cons const_cast(this)->EvalPhase(static_cast(i), phase); - if (prevAngle <= input.mAngle && input.mAngle <= phase.mAngle) { + if (prevAngle <= inputAngle && inputAngle <= phase.mAngle) { float deltaAngle = phase.mAngle - prevAngle; - if (0.0f <= deltaAngle * input.mDAngle) { + if (0.0f <= deltaAngle * inputDAngle) { if (deltaAngle != 0.0f) { - time = (input.mAngle - prevAngle) / deltaAngle + static_cast(i - 1); + time = (inputAngle - prevAngle) / deltaAngle + static_cast(i - 1); } else { time = static_cast(i - 1) + 0.5f; } @@ -518,7 +521,7 @@ bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) cons } } - float delta = input.mAngle - phase.mAngle; + float delta = inputAngle - phase.mAngle; if (delta < 0.0f) { delta = -delta; } From b33fa9750fd257ea224a541dc64db679a4aaab14 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 18:03:48 +0100 Subject: [PATCH 317/372] 87.54%: reshape FindMatchTime locals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 68619e50a..1c83abca7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -484,54 +484,66 @@ void FnRunBlender::AlignVel(float *vel) const { bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { PhaseValue phase; - float inputAngle = input.mAngle; - float inputDAngle = input.mDAngle; - float searchLength = input.mSearchLength; - float cycleLength = mWeight * (mCycles[1] - mCycles[0]) + mCycles[0]; - int searchCount = static_cast(cycleLength + cycleLength); + float a; + float da; + float na; + int n; + int s; + int i; + int minIdx; + float diffAngle; + float minAngle; + float angle = input.mAngle; + float dAngle = input.mDAngle; + FnRunBlender *nonConstThis = const_cast(this); + + n = FloatToInt((mWeight * (mCycles[1] - mCycles[0]) + mCycles[0]) * 2.0f); + s = 1; phase.mAngle = 0.0f; - if (0.0f < searchLength && searchLength < static_cast(searchCount)) { - searchCount = static_cast(searchLength) + 1; + if (0.0f < input.mSearchLength && input.mSearchLength < static_cast(n)) { + n = FloatToInt(input.mSearchLength) + s; } - const_cast(this)->EvalPhase(0.0f, phase); + nonConstThis->EvalPhase(0.0f, phase); - float bestDelta = inputAngle - phase.mAngle; - if (bestDelta < 0.0f) { - bestDelta = -bestDelta; + a = phase.mAngle; + minAngle = angle - a; + if (minAngle < 0.0f) { + minAngle = -minAngle; } - int bestIdx = 0; - for (int i = 1; i < searchCount; i++) { - float prevAngle = phase.mAngle; - - const_cast(this)->EvalPhase(static_cast(i), phase); + minIdx = 0; + for (i = s; i < n; i++) { + nonConstThis->EvalPhase(static_cast(i), phase); + na = phase.mAngle; - if (prevAngle <= inputAngle && inputAngle <= phase.mAngle) { - float deltaAngle = phase.mAngle - prevAngle; + if (a <= angle && angle <= na) { + da = na - a; - if (0.0f <= deltaAngle * inputDAngle) { - if (deltaAngle != 0.0f) { - time = (inputAngle - prevAngle) / deltaAngle + static_cast(i - 1); + if (0.0f <= da * dAngle) { + if (da != 0.0f) { + time = ((angle - a) / da + static_cast(i - s)) * static_cast(s); } else { - time = static_cast(i - 1) + 0.5f; + time = (static_cast(i - s) + 0.5f) * static_cast(s); } return true; } } - float delta = inputAngle - phase.mAngle; - if (delta < 0.0f) { - delta = -delta; + diffAngle = angle - na; + if (diffAngle < 0.0f) { + diffAngle = -diffAngle; } - if (delta < bestDelta) { - bestIdx = i; - bestDelta = delta; + if (diffAngle < minAngle) { + minIdx = i; + minAngle = diffAngle; } + + a = na; } - time = static_cast(bestIdx); + time = static_cast(minIdx); return true; } From 70e72fdb3f47eb3dca95ad72a7c295f1b61c68db Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 18:07:40 +0100 Subject: [PATCH 318/372] 87.58%: use temp vectors in AlignRootQ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 17 ++++++++--------- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 1c83abca7..5ddbb0dea 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -450,19 +450,18 @@ void FnRunBlender::AlignCycleBeginEnd(int cIdx) { } void FnRunBlender::AlignRootQ(float *sqt) const { + UMath::Vector4 result; float x = sqt[4]; float y = sqt[5]; float z = sqt[6]; float w = sqt[7]; - float newX = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); - float newY = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; - float newZ = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; - float newW = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; - - sqt[4] = newX; - sqt[5] = newY; - sqt[6] = newZ; - sqt[7] = newW; + + result.x = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); + result.y = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; + result.z = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; + result.w = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; + + *reinterpret_cast(&sqt[4]) = result; } void FnRunBlender::AlignVel(float *vel) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index dc8065049..9e4a6c963 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -280,19 +280,18 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { } void FnTurnBlender::AlignRootQ(float *sqt) const { + UMath::Vector4 result; float x = sqt[4]; float y = sqt[5]; float z = sqt[6]; float w = sqt[7]; - float newX = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); - float newY = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; - float newZ = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; - float newW = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; - - sqt[4] = newX; - sqt[5] = newY; - sqt[6] = newZ; - sqt[7] = newW; + + result.x = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); + result.y = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; + result.z = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; + result.w = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; + + *reinterpret_cast(&sqt[4]) = result; } void FnTurnBlender::AlignVel(float *vel) const { From 561154805f44135e3c157518f3ceb87094e5aca8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 18:30:27 +0100 Subject: [PATCH 319/372] 87.86%: restore AlignCycleBeginEnd quat multiply Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h | 7 +++++ .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 28 +++++++---------- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 31 +++++++------------ 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h b/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h index 323cdc982..034996e7b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h +++ b/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h @@ -93,6 +93,13 @@ inline void FastQuatBlendF4(float w, const float *d0, const float *d1, float *ou out[3] *= s; } +inline void QuatMult(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = (a.x * b.w - a.y * b.z) + a.z * b.y + a.w * b.x; + result.y = ((a.x * b.z + a.y * b.w) - a.z * b.x) + a.w * b.y; + result.z = ((-a.x) * b.y + a.y * b.x) + a.z * b.w + a.w * b.z; + result.w = ((-a.x * b.x - a.y * b.y) - a.z * b.z) + a.w * b.w; +} + }; // namespace EAGL4Anim #endif diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 5ddbb0dea..001579266 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -419,32 +419,26 @@ void FnRunBlender::AlignCycleBeginEnd(int cIdx) { if (!mInit) { mInit = true; mCycleIdx = -1; - mAlignQ.x = 0.0f; - mAlignQ.y = 0.0f; mAlignQ.z = 0.0f; mAlignQ.w = 1.0f; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; } else if (mCycleIdx != cIdx) { - float beginFacing[2]; - float endFacing[2]; + float v0[2]; + float v1[2]; UMath::Vector4 q; - PhaseChan *phase0 = const_cast(mPhases[mIdx]); - PhaseChan *phase1 = const_cast(mPhases[mIdx + 1]); + UMath::Vector4 resultQ; - BlendFacing(0.0f, 0.0f, beginFacing); - BlendFacing(static_cast(phase0->mNumFrames - 1), static_cast(phase1->mNumFrames - 1), endFacing); - ComputeAlignQ(beginFacing, endFacing, q); + BlendFacing(0.0f, 0.0f, v0); + BlendFacing(static_cast(const_cast(mPhases[mIdx])->mNumFrames - 1), + static_cast(const_cast(mPhases[mIdx + 1])->mNumFrames - 1), v1); + ComputeAlignQ(v0, v1, q); if (mCycleIdx - 1 == cIdx) { q.y = -q.y; } - float x = mAlignQ.x; - float y = mAlignQ.y; - float z = mAlignQ.z; - float w = mAlignQ.w; - mAlignQ.x = w * q.x + z * q.y + (x * q.w - y * q.z); - mAlignQ.w = w * q.w + ((-x * q.x - y * q.y) - z * q.z); - mAlignQ.y = w * q.y + ((x * q.z + y * q.w) - z * q.x); - mAlignQ.z = w * q.z + z * q.w - x * q.y + y * q.x; + QuatMult(mAlignQ, q, resultQ); + mAlignQ = resultQ; mCycleIdx = cIdx; } } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 9e4a6c963..dbe1a7283 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -249,33 +249,24 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { mAlignQ.z = 0.0f; mAlignQ.w = 1.0f; } else if (mCycleIdx != cIdx) { - float beginFacing[2]; - float endFacing[2]; + float v0[2]; + float v1[2]; UMath::Vector4 q; + UMath::Vector4 resultQ; + int i; - BlendBeginFacing(beginFacing); - BlendEndFacing(endFacing); - ComputeAlignQ(beginFacing, endFacing, q); + BlendBeginFacing(v0); + BlendEndFacing(v1); + ComputeAlignQ(v0, v1, q); if (mCycleIdx - 1 == cIdx) { q.y = -q.y; } - float x = mAlignQ.x; - float y = mAlignQ.y; - float z = mAlignQ.z; - float w = mAlignQ.w; - float newX = (x * q.w - y * q.z) + z * q.y + w * q.x; - float newY = ((x * q.z + y * q.w) - z * q.x) + w * q.y; - float newZ = -x * q.y + y * q.x + z * q.w + w * q.z; - float newW = ((-x * q.x - y * q.y) - z * q.z) + w * q.w; - - i_6840++; - mAlignQ.x = newX; - mAlignQ.y = newY; - mAlignQ.z = newZ; - mAlignQ.w = newW; + QuatMult(mAlignQ, q, resultQ); + i = i_6840++; + mAlignQ = resultQ; mCycleIdx = cIdx; - printf("turn align[%d] Q: %g %g %g %g\n\n", i_6840 - 1, mAlignQ.x, mAlignQ.y, mAlignQ.z, mAlignQ.w); + printf("turn align[%d] Q: %g %g %g %g\n\n", i, mAlignQ.x, mAlignQ.y, mAlignQ.z, mAlignQ.w); } } From d6f388fba883ad3e20a4699c3392345167fe5867 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 18:45:35 +0100 Subject: [PATCH 320/372] 88.21%: use QuatTransformPoint in turn facing blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 24 +++---- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 68 +++++++------------ 2 files changed, 36 insertions(+), 56 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 001579266..18e83423c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -18,19 +18,19 @@ namespace EAGL4Anim { static const UMath::Vector4 kFacingAxis = {0.0f, 1.0f, 0.0f, 1.0f}; static inline void QuatTransformPoint(const UMath::Vector4 &q, const UMath::Vector4 &p, UMath::Vector4 &result) { - float zz; - float yz; - float yy; - float xz; - float xy; - float xx; - float wz; - float wy; - float wx; - float zs; - float ys; - float xs; float s; + float xs; + float ys; + float zs; + float wx; + float wy; + float wz; + float xx; + float xy; + float xz; + float yy; + float yz; + float zz; s = 2.0f; xs = q.x * s; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index dbe1a7283..e11263057 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -303,6 +303,8 @@ void FnTurnBlender::AlignVel(float *vel) const { } bool FnTurnBlender::BlendBeginFacing(float *f) const { + FnRunBlender *fnA; + if (!f) { return false; } @@ -310,35 +312,25 @@ bool FnTurnBlender::BlendBeginFacing(float *f) const { UMath::Vector4 q0; UMath::Vector4 q1; UMath::Vector4 q; + UMath::Vector4 xAxis; + UMath::Vector4 xAxis1; - reinterpret_cast(mFnAnims[0])->ComputeBeginRootQ(q0); - reinterpret_cast(mFnAnims[1])->ComputeBeginRootQ(q1); + fnA = reinterpret_cast(mFnAnims[0]); + fnA->ComputeBeginRootQ(q0); + fnA = reinterpret_cast(mFnAnims[1]); + fnA->ComputeBeginRootQ(q1); FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); - - UMath::Vector4 oldF; - oldF.x = 0.0f; - oldF.y = 1.0f; - oldF.z = 0.0f; - oldF.w = 1.0f; - - float doubleY = q.y + q.y; - float doubleZ = q.z + q.z; - float xx2 = q.x * (q.x + q.x); - float wx2 = q.w * (q.x + q.x); - float newY = oldF.x * (q.x * doubleZ - q.w * doubleY) + oldF.y * (q.y * doubleZ + wx2) + - oldF.z * (oldF.w - (xx2 + q.y * doubleY)); - float newX = oldF.x * (oldF.w - (q.y * doubleY + q.z * doubleZ)) + oldF.y * (q.x * doubleY - q.w * doubleZ) + - oldF.z * (q.x * doubleZ + q.w * doubleY); - float newZ = oldF.x * (q.x * doubleY + q.w * doubleZ) + oldF.y * (oldF.w - (xx2 + q.z * doubleZ)) + - oldF.z * (q.y * doubleZ - wx2); - - f[1] = newY; - f[0] = newX; + xAxis = kFacingAxis; + QuatTransformPoint(q, xAxis, xAxis1); + f[0] = xAxis1.x; + f[1] = xAxis1.z; printf("Facing: %g %g\n", f[0], f[1]); return true; } bool FnTurnBlender::BlendEndFacing(float *f) const { + FnRunBlender *fnA; + if (!f) { return false; } @@ -346,30 +338,18 @@ bool FnTurnBlender::BlendEndFacing(float *f) const { UMath::Vector4 q0; UMath::Vector4 q1; UMath::Vector4 q; + UMath::Vector4 xAxis; + UMath::Vector4 xAxis1; - reinterpret_cast(mFnAnims[0])->ComputeEndRootQ(q0); - reinterpret_cast(mFnAnims[1])->ComputeEndRootQ(q1); + fnA = reinterpret_cast(mFnAnims[0]); + fnA->ComputeEndRootQ(q0); + fnA = reinterpret_cast(mFnAnims[1]); + fnA->ComputeEndRootQ(q1); FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); - - UMath::Vector4 oldF; - oldF.x = 0.0f; - oldF.y = 1.0f; - oldF.z = 0.0f; - oldF.w = 1.0f; - - float doubleY = q.y + q.y; - float doubleZ = q.z + q.z; - float xx2 = q.x * (q.x + q.x); - float wx2 = q.w * (q.x + q.x); - float newY = oldF.x * (q.x * doubleZ - q.w * doubleY) + oldF.y * (q.y * doubleZ + wx2) + - oldF.z * (oldF.w - (xx2 + q.y * doubleY)); - float newX = oldF.x * (oldF.w - (q.y * doubleY + q.z * doubleZ)) + oldF.y * (q.x * doubleY - q.w * doubleZ) + - oldF.z * (q.x * doubleZ + q.w * doubleY); - float newZ = oldF.x * (q.x * doubleY + q.w * doubleZ) + oldF.y * (oldF.w - (xx2 + q.z * doubleZ)) + - oldF.z * (q.y * doubleZ - wx2); - - f[1] = newY; - f[0] = newX; + xAxis = kFacingAxis; + QuatTransformPoint(q, xAxis, xAxis1); + f[0] = xAxis1.x; + f[1] = xAxis1.z; printf("Facing: %g %g\n", f[0], f[1]); return true; } From 3aff2ec16ed2a6fea22a603f4e9b81467cf627e6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 18:59:15 +0100 Subject: [PATCH 321/372] 88.58%: match blender align helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 36 +++++++------------ .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 35 ++++++------------ 2 files changed, 23 insertions(+), 48 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 18e83423c..811aed6b4 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -48,6 +48,7 @@ static inline void QuatTransformPoint(const UMath::Vector4 &q, const UMath::Vect result.x = p.x * (p.w - (yy + zz)) + p.y * (xy - wz) + p.z * (xz + wy); result.y = p.x * (xy + wz) + p.y * (p.w - (xx + zz)) + p.z * (yz - wx); result.z = p.x * (xz - wy) + p.y * (yz + wx) + p.z * (p.w - (xx + yy)); + result.w = p.w; } FnRunBlender::FnRunBlender() @@ -445,34 +446,21 @@ void FnRunBlender::AlignCycleBeginEnd(int cIdx) { void FnRunBlender::AlignRootQ(float *sqt) const { UMath::Vector4 result; - float x = sqt[4]; - float y = sqt[5]; - float z = sqt[6]; - float w = sqt[7]; - - result.x = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); - result.y = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; - result.z = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; - result.w = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; - + QuatMult(*reinterpret_cast(&sqt[4]), mAlignQ, result); *reinterpret_cast(&sqt[4]) = result; } void FnRunBlender::AlignVel(float *vel) const { - float x = mAlignQ.x; - float y = mAlignQ.y; - float z = mAlignQ.z; - float w = mAlignQ.w; - float doubleY = y + y; - float doubleZ = z + z; - float oldX = vel[0]; - float oldY = vel[1]; - float y2 = y * doubleY; - float xz = x * doubleZ; - float wy = w * doubleY; - - vel[0] = oldX * (1.0f - (z * doubleZ + y2)) + oldY * (xz + wy); - vel[1] = oldX * (xz - wy) + oldY * (1.0f - (x * (x + x) + y2)); + UMath::Vector4 org; + UMath::Vector4 result; + + org.x = vel[0]; + org.y = 0.0f; + org.z = vel[1]; + org.w = 1.0f; + QuatTransformPoint(mAlignQ, org, result); + vel[0] = result.x; + vel[1] = result.z; } bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index e11263057..d3604ee29 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -272,34 +272,21 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { void FnTurnBlender::AlignRootQ(float *sqt) const { UMath::Vector4 result; - float x = sqt[4]; - float y = sqt[5]; - float z = sqt[6]; - float w = sqt[7]; - - result.x = w * mAlignQ.x + z * mAlignQ.y + (x * mAlignQ.w - y * mAlignQ.z); - result.y = w * mAlignQ.y + (x * mAlignQ.z + y * mAlignQ.w) - z * mAlignQ.x; - result.z = w * mAlignQ.z + z * mAlignQ.w - x * mAlignQ.y + y * mAlignQ.x; - result.w = w * mAlignQ.w - x * mAlignQ.x - y * mAlignQ.y - z * mAlignQ.z; - + QuatMult(*reinterpret_cast(&sqt[4]), mAlignQ, result); *reinterpret_cast(&sqt[4]) = result; } void FnTurnBlender::AlignVel(float *vel) const { - float x = mAlignQ.x; - float y = mAlignQ.y; - float z = mAlignQ.z; - float w = mAlignQ.w; - float doubleY = y + y; - float doubleZ = z + z; - float oldX = vel[0]; - float oldY = vel[1]; - float y2 = y * doubleY; - float xz = x * doubleZ; - float wy = w * doubleY; - - vel[0] = oldX * (1.0f - (z * doubleZ + y2)) + oldY * (xz + wy); - vel[1] = oldX * (xz - wy) + oldY * (1.0f - (x * (x + x) + y2)); + UMath::Vector4 org; + UMath::Vector4 result; + + org.x = vel[0]; + org.y = 0.0f; + org.z = vel[1]; + org.w = 1.0f; + QuatTransformPoint(mAlignQ, org, result); + vel[0] = result.x; + vel[1] = result.z; } bool FnTurnBlender::BlendBeginFacing(float *f) const { From 3d7533cd45093f40dfcf6a25fce3bf2bb1f9d8b1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 19:27:13 +0100 Subject: [PATCH 322/372] 88.584%: move transform owner and tighten GetSymbol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/SourceLists/zEagl4Anim.cpp | 2 + .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 145 ----------------- .../Src/EAGL4Anim/eagl4runtimetransform.cpp | 147 ++++++++++++++++++ .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 10 +- 4 files changed, 150 insertions(+), 154 deletions(-) diff --git a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp index dc95d275e..84c0ddbc0 100644 --- a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp +++ b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp @@ -38,6 +38,8 @@ #include "Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp" +#include "Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp" + #include "Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp" #include "Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp" diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 0d0a7e7d9..ee39d6866 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -8,151 +8,6 @@ static inline void LinearBlendF3(float w, const float *d0, const float *d1, floa out[2] = d0[2] + w * (d1[2] - d0[2]); } - -namespace EAGL4 { - -static inline void EAGL4m3toquat(const UMath::Matrix3 *mat, UMath::Vector4 *result) { - float s; - float trace = (*mat)[0][0] + (*mat)[1][1] + (*mat)[2][2]; - - if (trace > 0.0f) { - s = EAGL4Anim::FastSqrt(trace + 1.0f); - result->w = s * 0.5f; - s = 0.5f / s; - result->x = ((*mat)[1][2] - (*mat)[2][1]) * s; - result->y = ((*mat)[2][0] - (*mat)[0][2]) * s; - result->z = ((*mat)[0][1] - (*mat)[1][0]) * s; - } else { - unsigned long i = 0; - - if ((*mat)[1][1] > (*mat)[0][0]) { - i = 1; - } - if ((*mat)[2][2] > (*mat)[i][i]) { - i = 2; - } - - if (i == 0) { - s = EAGL4Anim::FastSqrt(((*mat)[0][0] - ((*mat)[1][1] + (*mat)[2][2])) + 1.0f); - result->x = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - result->w = ((*mat)[1][2] - (*mat)[2][1]) * s; - result->y = ((*mat)[0][1] + (*mat)[1][0]) * s; - result->z = ((*mat)[0][2] + (*mat)[2][0]) * s; - } else if (i == 1) { - s = EAGL4Anim::FastSqrt(((*mat)[1][1] - ((*mat)[2][2] + (*mat)[0][0])) + 1.0f); - result->y = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - result->w = ((*mat)[2][0] - (*mat)[0][2]) * s; - result->x = ((*mat)[1][0] + (*mat)[0][1]) * s; - result->z = ((*mat)[1][2] + (*mat)[2][1]) * s; - } else { - s = EAGL4Anim::FastSqrt(((*mat)[2][2] - ((*mat)[0][0] + (*mat)[1][1])) + 1.0f); - result->z = s * 0.5f; - if (s != 0.0f) { - s = 0.5f / s; - } - result->w = ((*mat)[0][1] - (*mat)[1][0]) * s; - result->x = ((*mat)[2][0] + (*mat)[0][2]) * s; - result->y = ((*mat)[2][1] + (*mat)[1][2]) * s; - } - } -} - -void MultMatrix(const UMath::Matrix4 *pm1, const UMath::Matrix4 *pm2, UMath::Matrix4 *presult) { - const float *m1 = pm1->GetElements(); - const float *m2 = pm2->GetElements(); - float *result = presult->GetElements(); - - result[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + m1[3] * m2[12]; - result[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + m1[3] * m2[13]; - result[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + m1[3] * m2[14]; - result[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3] * m2[15]; - result[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + m1[7] * m2[12]; - result[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + m1[7] * m2[13]; - result[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + m1[7] * m2[14]; - result[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7] * m2[15]; - result[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + m1[11] * m2[12]; - result[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + m1[11] * m2[13]; - result[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + m1[11] * m2[14]; - result[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11] * m2[15]; - result[12] = m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + m1[15] * m2[12]; - result[13] = m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + m1[15] * m2[13]; - result[14] = m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + m1[15] * m2[14]; - result[15] = m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + m1[15] * m2[15]; -} - -void Transform::PostMult(const Transform &second, Transform *pOutput) const { - MultMatrix(&m, &second.m, &pOutput->m); -} - -void Transform::PostMult(const Transform &second) { - Transform result; - - MultMatrix(&m, &second.m, &result.m); - m = result.m; -} - -void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTrans) const { - UMath::Matrix3 m3; - - m3.v0.x = m.v0.x; - m3.v0.y = m.v0.y; - m3.v0.z = m.v0.z; - m3.v1.x = m.v1.x; - m3.v1.y = m.v1.y; - m3.v1.z = m.v1.z; - m3.v2.x = m.v2.x; - m3.v2.y = m.v2.y; - m3.v2.z = m.v2.z; - - EAGL4m3toquat(&m3, retQuat); - - retTrans->x = m.v3.x; - retTrans->y = m.v3.y; - retTrans->z = m.v3.z; - retTrans->w = m.v3.w; -} - -void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float qz, float qw, float tx, float ty, float tz) { - float s = 1.0f; - float xs = qx + qx; - float ys = qy + qy; - float zs = qz + qz; - float wx = qw * xs; - float wy = qw * ys; - float wz = qw * zs; - float xx = qx * xs; - float xy = qx * ys; - float xz = qx * zs; - float yy = qy * ys; - float yz = qy * zs; - float zz = qz * zs; - - m.v3.x = tx; - m.v3.y = ty; - m.v3.w = s; - m.v2.w = 0.0f; - m.v0.w = 0.0f; - m.v1.w = 0.0f; - m.v3.z = tz; - m.v0.z = sx * (xz - wy); - m.v1.z = sy * (yz + wx); - m.v2.z = sz * (s - (xx + yy)); - m.v0.x = sx * (s - (yy + zz)); - m.v1.x = sy * (xy - wz); - m.v2.x = sz * (xz + wy); - m.v0.y = sx * (xy + wz); - m.v1.y = sy * (s - (xx + zz)); - m.v2.y = sz * (yz - wx); -} - -}; // namespace EAGL4 - namespace EAGL4Anim { namespace { diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp index e69de29bb..4380eff87 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp @@ -0,0 +1,147 @@ +#include "Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.h" + +#include "AnimUtil.h" + +namespace EAGL4 { + +static inline void EAGL4m3toquat(const UMath::Matrix3 *mat, UMath::Vector4 *result) { + float s; + float trace = (*mat)[0][0] + (*mat)[1][1] + (*mat)[2][2]; + + if (trace > 0.0f) { + s = EAGL4Anim::FastSqrt(trace + 1.0f); + result->w = s * 0.5f; + s = 0.5f / s; + result->x = ((*mat)[1][2] - (*mat)[2][1]) * s; + result->y = ((*mat)[2][0] - (*mat)[0][2]) * s; + result->z = ((*mat)[0][1] - (*mat)[1][0]) * s; + } else { + unsigned long i = 0; + + if ((*mat)[1][1] > (*mat)[0][0]) { + i = 1; + } + if ((*mat)[2][2] > (*mat)[i][i]) { + i = 2; + } + + if (i == 0) { + s = EAGL4Anim::FastSqrt(((*mat)[0][0] - ((*mat)[1][1] + (*mat)[2][2])) + 1.0f); + result->x = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[1][2] - (*mat)[2][1]) * s; + result->y = ((*mat)[0][1] + (*mat)[1][0]) * s; + result->z = ((*mat)[0][2] + (*mat)[2][0]) * s; + } else if (i == 1) { + s = EAGL4Anim::FastSqrt(((*mat)[1][1] - ((*mat)[2][2] + (*mat)[0][0])) + 1.0f); + result->y = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[2][0] - (*mat)[0][2]) * s; + result->x = ((*mat)[1][0] + (*mat)[0][1]) * s; + result->z = ((*mat)[1][2] + (*mat)[2][1]) * s; + } else { + s = EAGL4Anim::FastSqrt(((*mat)[2][2] - ((*mat)[0][0] + (*mat)[1][1])) + 1.0f); + result->z = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[0][1] - (*mat)[1][0]) * s; + result->x = ((*mat)[2][0] + (*mat)[0][2]) * s; + result->y = ((*mat)[2][1] + (*mat)[1][2]) * s; + } + } +} + +void MultMatrix(const UMath::Matrix4 *pm1, const UMath::Matrix4 *pm2, UMath::Matrix4 *presult) { + const float *m1 = pm1->GetElements(); + const float *m2 = pm2->GetElements(); + float *result = presult->GetElements(); + + result[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + m1[3] * m2[12]; + result[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + m1[3] * m2[13]; + result[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + m1[3] * m2[14]; + result[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3] * m2[15]; + result[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + m1[7] * m2[12]; + result[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + m1[7] * m2[13]; + result[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + m1[7] * m2[14]; + result[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7] * m2[15]; + result[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + m1[11] * m2[12]; + result[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + m1[11] * m2[13]; + result[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + m1[11] * m2[14]; + result[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11] * m2[15]; + result[12] = m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + m1[15] * m2[12]; + result[13] = m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + m1[15] * m2[13]; + result[14] = m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + m1[15] * m2[14]; + result[15] = m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + m1[15] * m2[15]; +} + +void Transform::PostMult(const Transform &second, Transform *pOutput) const { + MultMatrix(&m, &second.m, &pOutput->m); +} + +void Transform::PostMult(const Transform &second) { + Transform result; + + MultMatrix(&m, &second.m, &result.m); + m = result.m; +} + +void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTrans) const { + UMath::Matrix3 m3; + + m3.v0.x = m.v0.x; + m3.v0.y = m.v0.y; + m3.v0.z = m.v0.z; + m3.v1.x = m.v1.x; + m3.v1.y = m.v1.y; + m3.v1.z = m.v1.z; + m3.v2.x = m.v2.x; + m3.v2.y = m.v2.y; + m3.v2.z = m.v2.z; + + EAGL4m3toquat(&m3, retQuat); + + retTrans->x = m.v3.x; + retTrans->y = m.v3.y; + retTrans->z = m.v3.z; + retTrans->w = m.v3.w; +} + +void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float qz, float qw, float tx, float ty, float tz) { + float s = 1.0f; + float xs = qx + qx; + float ys = qy + qy; + float zs = qz + qz; + float wx = qw * xs; + float wy = qw * ys; + float wz = qw * zs; + float xx = qx * xs; + float xy = qx * ys; + float xz = qx * zs; + float yy = qy * ys; + float yz = qy * zs; + float zz = qz * zs; + + m.v3.x = tx; + m.v3.y = ty; + m.v3.w = s; + m.v2.w = 0.0f; + m.v0.w = 0.0f; + m.v1.w = 0.0f; + m.v3.z = tz; + m.v0.z = sx * (xz - wy); + m.v1.z = sy * (yz + wx); + m.v2.z = sz * (s - (xx + yy)); + m.v0.x = sx * (s - (yy + zz)); + m.v1.x = sy * (xy - wz); + m.v2.x = sz * (xz + wy); + m.v0.y = sx * (xy + wz); + m.v1.y = sy * (s - (xx + zz)); + m.v2.y = sz * (yz - wx); +} + +}; // namespace EAGL4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 305766767..5c5e598ca 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -567,22 +567,14 @@ DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { DynamicLoader::Symbol r; HashPointer *h = reinterpret_cast(handle); if (!h) { - // r.name = nullptr; - r.type = nullptr; - // r.data = nullptr; - r.isInternalRef = false; return r; } ELF32_Sym *s = h->symtab; if (i < 0 || i >= h->symbols_num) { - // r.name = nullptr; - r.type = nullptr; - // r.data = nullptr; - r.isInternalRef = false; return r; } r.name = &h->strtab[s[i].st_name]; - r.type = &r.name[strlen(&h->strtab[s[i].st_name])]; + r.type = r.name + strlen(r.name); if (r.type[1] == 0x7F) { r.type += 2; } From f58fa23e7f0133fbe54e2072d03ea8450b964737 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 19:36:21 +0100 Subject: [PATCH 323/372] 88.624%: improve turn SetWeight and GetSymbol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 42 +++++++++---------- .../Src/EAGL4Anim/eagl4supportdlopen.cpp | 17 ++++---- .../Indep/Src/EAGL4Anim/eagl4supportdlopen.h | 9 ++-- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index d3604ee29..88da01514 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -78,44 +78,42 @@ void FnTurnBlender::Eval(float prevTime, float currTime, float *pose) { } void FnTurnBlender::SetWeight(float w) { - int idx = static_cast(w); - int oldIdx = mIdx; + int i = FloatToInt(w); - if (idx < 0) { - idx = 0; + if (i < 0) { + i = 0; } - if (idx >= mNumAnims - 1) { - idx = mNumAnims - 2; + if (i >= mNumAnims - 1) { + i = mNumAnims - 2; } - mWeight = w - static_cast(idx); - if (idx == oldIdx) { + mWeight = w - static_cast(i); + if (i == mIdx) { float prevFreq = mFreq; mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; } else { - if (idx == oldIdx + 1) { + if (i == mIdx + 1) { mFnAnims[0] = mFnAnims[1]; - mFnAnims[1] = mAnims[idx + 1]; - } else if (idx == oldIdx - 1) { + mFnAnims[1] = mAnims[i + 1]; + } else if (i == mIdx - 1) { mFnAnims[1] = mFnAnims[0]; - mFnAnims[0] = mAnims[idx]; + mFnAnims[0] = mAnims[i]; } else { - mFnAnims[0] = mAnims[idx]; - mFnAnims[1] = mAnims[idx + 1]; + mFnAnims[0] = mAnims[i]; + mFnAnims[1] = mAnims[i + 1]; } - mIdx = idx; - - FnRunBlender *anim0 = static_cast(mAnims[idx]); - FnRunBlender *anim1 = static_cast(mAnims[mIdx + 1]); + mIdx = i; + FnRunBlender *fnA = static_cast(mAnims[i]); float prevFreq = mFreq; - mCycles[0] = 1.0f / anim0->GetFrequency(); - mOffsets[0] = anim0->GetOffset(); - mCycles[1] = 1.0f / anim1->GetFrequency(); - mOffsets[1] = anim1->GetOffset(); + mCycles[0] = 1.0f / fnA->GetFrequency(); + mOffsets[0] = fnA->GetOffset(); + fnA = static_cast(mAnims[mIdx + 1]); + mCycles[1] = 1.0f / fnA->GetFrequency(); + mOffsets[1] = fnA->GetOffset(); mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 5c5e598ca..d9d832ca0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -565,22 +565,25 @@ int DynamicLoader::GetCount() const { // TODO DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { DynamicLoader::Symbol r; - HashPointer *h = reinterpret_cast(handle); + HashPointer *h; + ELF32_Sym *s; + int iIndex; + + h = reinterpret_cast(handle); if (!h) { return r; } - ELF32_Sym *s = h->symtab; + s = h->symtab; if (i < 0 || i >= h->symbols_num) { return r; } r.name = &h->strtab[s[i].st_name]; - r.type = r.name + strlen(r.name); - if (r.type[1] == 0x7F) { - r.type += 2; + r.type = &r.name[strlen(r.name) + 1]; + if (r.type[0] == 0x7F) { + r.type++; } r.isInternalRef = (s[i].st_other - 2) > 3; - - int iIndex = s[i].st_shndx; + iIndex = s[i].st_shndx; if (s[i].st_other == 1) { r.data = reinterpret_cast(s[i].st_value); } else if (iIndex > 0 && iIndex < h->e->e_shnum) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h index e52c96a1b..eeb5bdecc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h @@ -19,11 +19,10 @@ class DynamicLoader { public: // total size: 0x10 struct Symbol { - // TODO is this constructor correct? or is it empty maybe? - Symbol() - : name(nullptr), // - data(nullptr) // - {} + Symbol() { + name = nullptr; + data = nullptr; + } const char *name; // offset 0x0, size 0x4 const char *type; // offset 0x4, size 0x4 From cc6857854a66b717b78d79d68ef0178e61360e74 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 19:39:09 +0100 Subject: [PATCH 324/372] 88.657%: match turn SetWeight Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 88da01514..632bba08c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -78,7 +78,9 @@ void FnTurnBlender::Eval(float prevTime, float currTime, float *pose) { } void FnTurnBlender::SetWeight(float w) { + float prevFreq; int i = FloatToInt(w); + FnRunBlender *fnA; if (i < 0) { i = 0; @@ -89,8 +91,7 @@ void FnTurnBlender::SetWeight(float w) { mWeight = w - static_cast(i); if (i == mIdx) { - float prevFreq = mFreq; - + prevFreq = mFreq; mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; } else { @@ -106,13 +107,12 @@ void FnTurnBlender::SetWeight(float w) { } mIdx = i; - FnRunBlender *fnA = static_cast(mAnims[i]); - float prevFreq = mFreq; - + fnA = static_cast(mAnims[i]); mCycles[0] = 1.0f / fnA->GetFrequency(); mOffsets[0] = fnA->GetOffset(); fnA = static_cast(mAnims[mIdx + 1]); mCycles[1] = 1.0f / fnA->GetFrequency(); + prevFreq = mFreq; mOffsets[1] = fnA->GetOffset(); mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; From 163ef404637e23e3cff0347a369c945569c49a40 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 19:55:50 +0100 Subject: [PATCH 325/372] 88.684%: improve run SetWeight Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 811aed6b4..c6da0398a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -205,10 +205,10 @@ void FnRunBlender::SetWeight(float w) { prevFreq = mFreq; - if (!mPhases[i]->StartWithRight()) { - mAlignFrame[0] = static_cast(mPhases[i]->mStartTime + mPhases[i]->mCycles[0]); - } else { + if (mPhases[i]->StartWithRight()) { mAlignFrame[0] = static_cast(mPhases[i]->mStartTime); + } else { + mAlignFrame[0] = static_cast(mPhases[i]->mStartTime + mPhases[i]->mCycles[0]); } mAlignFrame[1] = static_cast(mPhases[i + 1]->mStartTime); From 8611af9bc9bd7909452ce73903f06515a1f80d85 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 20:01:44 +0100 Subject: [PATCH 326/372] 88.711%: match run SetWeight Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index c6da0398a..9ec63ec74 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -203,8 +203,6 @@ void FnRunBlender::SetWeight(float w) { } } - prevFreq = mFreq; - if (mPhases[i]->StartWithRight()) { mAlignFrame[0] = static_cast(mPhases[i]->mStartTime); } else { @@ -213,10 +211,11 @@ void FnRunBlender::SetWeight(float w) { mAlignFrame[1] = static_cast(mPhases[i + 1]->mStartTime); if (!mPhases[i + 1]->StartWithRight()) { - mAlignFrame[1] += static_cast(mPhases[i + 1]->mCycles[0]); + mAlignFrame[1] += static_cast(static_cast(mPhases[i + 1]->mCycles[0])); } mIdx = i; + prevFreq = mFreq; mCycles[0] = static_cast(mPhases[i]->mCycles[0] + mPhases[i]->mCycles[1]) * 0.5f; mCycles[1] = static_cast(mPhases[i + 1]->mCycles[0] + mPhases[i + 1]->mCycles[1]) * 0.5f; mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; From c0cde48a11c9f77ec9d6d047a75831b3981643bb Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 20:27:47 +0100 Subject: [PATCH 327/372] 88.732%: improve GetSymbol initialization Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index d9d832ca0..b9a3e540e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -569,6 +569,8 @@ DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { ELF32_Sym *s; int iIndex; + r.name = nullptr; + r.data = nullptr; h = reinterpret_cast(handle); if (!h) { return r; From 53676c7af348f8cc2ee4a98610283f533215fd1a Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 20:41:53 +0100 Subject: [PATCH 328/372] 88.757%: improve GetSymbol type decode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index b9a3e540e..7f7e35e77 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -565,13 +565,12 @@ int DynamicLoader::GetCount() const { // TODO DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { DynamicLoader::Symbol r; - HashPointer *h; + HashPointer *h = reinterpret_cast(handle); ELF32_Sym *s; int iIndex; r.name = nullptr; r.data = nullptr; - h = reinterpret_cast(handle); if (!h) { return r; } @@ -580,11 +579,13 @@ DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { return r; } r.name = &h->strtab[s[i].st_name]; - r.type = &r.name[strlen(r.name) + 1]; + r.type = r.name + strlen(r.name) + 1; if (r.type[0] == 0x7F) { r.type++; + } else { + r.type--; } - r.isInternalRef = (s[i].st_other - 2) > 3; + r.isInternalRef = s[i].st_other > 5; iIndex = s[i].st_shndx; if (s[i].st_other == 1) { r.data = reinterpret_cast(s[i].st_value); From ab5a2a234272b56142cd7dafe9309f83b0b3c13f Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:05:45 +0100 Subject: [PATCH 329/372] 88.832%: improve FnStatelessQ quaternion unpack Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 077e329ab..14c313db8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -17,6 +17,8 @@ static inline float UncompressStatelessQValue(unsigned short value) { return bits.f; } +#define STORE_STATELESS_Q_BITS(dst, value) *reinterpret_cast(&(dst)) = (((value) & 0x8000) << 16) | (((value) & 0x7FFF) << 15) + static inline float *GetStatelessQOutput(float *sqt, unsigned char boneIdx) { return &sqt[boneIdx * 12 + 4]; } @@ -130,20 +132,23 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) int index; if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { - unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, floorKey + 1); + int nextKey = floorKey + 1; + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); for (int ibone = 0; ibone < nBones; ibone++) { UMath::Vector4 prevQ; UMath::Vector4 nextQ; - prevQ.x = UncompressStatelessQValue(*frameData++); - prevQ.y = UncompressStatelessQValue(*frameData++); - prevQ.z = UncompressStatelessQValue(*frameData++); - prevQ.w = UncompressStatelessQValue(*frameData++); - nextQ.x = UncompressStatelessQValue(*nextFrameData++); - nextQ.y = UncompressStatelessQValue(*nextFrameData++); - nextQ.z = UncompressStatelessQValue(*nextFrameData++); - nextQ.w = UncompressStatelessQValue(*nextFrameData++); + STORE_STATELESS_Q_BITS(prevQ.x, frameData[0]); + STORE_STATELESS_Q_BITS(prevQ.y, frameData[1]); + STORE_STATELESS_Q_BITS(prevQ.z, frameData[2]); + STORE_STATELESS_Q_BITS(prevQ.w, frameData[3]); + frameData += 4; + STORE_STATELESS_Q_BITS(nextQ.x, nextFrameData[0]); + STORE_STATELESS_Q_BITS(nextQ.y, nextFrameData[1]); + STORE_STATELESS_Q_BITS(nextQ.z, nextFrameData[2]); + STORE_STATELESS_Q_BITS(nextQ.w, nextFrameData[3]); + nextFrameData += 4; index = boneIdxs[ibone] * 12; q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; @@ -155,25 +160,27 @@ bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) for (int ibone = 0; ibone < nBones; ibone++) { index = boneIdxs[ibone] * 12; - q[index + 0] = UncompressStatelessQValue(*frameData++); - q[index + 1] = UncompressStatelessQValue(*frameData++); - q[index + 2] = UncompressStatelessQValue(*frameData++); - q[index + 3] = UncompressStatelessQValue(*frameData++); + STORE_STATELESS_Q_BITS(q[index + 0], frameData[0]); + STORE_STATELESS_Q_BITS(q[index + 1], frameData[1]); + STORE_STATELESS_Q_BITS(q[index + 2], frameData[2]); + STORE_STATELESS_Q_BITS(q[index + 3], frameData[3]); + frameData += 4; } } if (statelessQ->mNumConstBones != 0) { int numConsts = statelessQ->mNumConstBones; - unsigned short *constBuf = statelessQ->GetConstData(dataBuf); unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); + unsigned short *constBuf = statelessQ->GetConstData(dataBuf); for (int ibone = 0; ibone < numConsts; ibone++) { index = *constIdxs++ * 12; - q[index + 0] = UncompressStatelessQValue(*constBuf++); - q[index + 1] = UncompressStatelessQValue(*constBuf++); - q[index + 2] = UncompressStatelessQValue(*constBuf++); - q[index + 3] = UncompressStatelessQValue(*constBuf++); + STORE_STATELESS_Q_BITS(q[index + 0], constBuf[0]); + STORE_STATELESS_Q_BITS(q[index + 1], constBuf[1]); + STORE_STATELESS_Q_BITS(q[index + 2], constBuf[2]); + STORE_STATELESS_Q_BITS(q[index + 3], constBuf[3]); + constBuf += 4; } } } else { From 35a550270aada5a408e3b9043d554d52a5d84073 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:06:44 +0100 Subject: [PATCH 330/372] 88.941%: improve FnStatelessQ mask unpack Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 14c313db8..3efb68353 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -221,8 +221,14 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool prevData = &frameData[ibone * 4]; nextData = &nextFrameData[ibone * 4]; - LoadStatelessQ(prevData, prevQ); - LoadStatelessQ(nextData, nextQ); + STORE_STATELESS_Q_BITS(prevQ.x, prevData[0]); + STORE_STATELESS_Q_BITS(prevQ.y, prevData[1]); + STORE_STATELESS_Q_BITS(prevQ.z, prevData[2]); + STORE_STATELESS_Q_BITS(prevQ.w, prevData[3]); + STORE_STATELESS_Q_BITS(nextQ.x, nextData[0]); + STORE_STATELESS_Q_BITS(nextQ.y, nextData[1]); + STORE_STATELESS_Q_BITS(nextQ.z, nextData[2]); + STORE_STATELESS_Q_BITS(nextQ.w, nextData[3]); index = boneIdxs[ibone] * 12; q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; @@ -238,10 +244,10 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool index = boneIdxs[ibone] * 12; - q[index + 0] = UncompressStatelessQValue(*prevData++); - q[index + 1] = UncompressStatelessQValue(*prevData++); - q[index + 2] = UncompressStatelessQValue(*prevData++); - q[index + 3] = UncompressStatelessQValue(*prevData); + STORE_STATELESS_Q_BITS(q[index + 0], prevData[0]); + STORE_STATELESS_Q_BITS(q[index + 1], prevData[1]); + STORE_STATELESS_Q_BITS(q[index + 2], prevData[2]); + STORE_STATELESS_Q_BITS(q[index + 3], prevData[3]); } } } @@ -257,10 +263,10 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool index = constIdxs[ibone] * 12; - q[index + 0] = UncompressStatelessQValue(*prevData++); - q[index + 1] = UncompressStatelessQValue(*prevData++); - q[index + 2] = UncompressStatelessQValue(*prevData++); - q[index + 3] = UncompressStatelessQValue(*prevData); + STORE_STATELESS_Q_BITS(q[index + 0], prevData[0]); + STORE_STATELESS_Q_BITS(q[index + 1], prevData[1]); + STORE_STATELESS_Q_BITS(q[index + 2], prevData[2]); + STORE_STATELESS_Q_BITS(q[index + 3], prevData[3]); } } } From 736f857cf2e15ad3f11a7527dc7d8ad6bd68d0af Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:10:34 +0100 Subject: [PATCH 331/372] 89.076%: simplify RawEventChannel event scan Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawEventChannel.cpp | 101 ++++++------------ 1 file changed, 31 insertions(+), 70 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp index eeaccfe95..21febaf9f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp @@ -5,105 +5,66 @@ namespace EAGL4Anim { void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tIdx, float &cacheCurrentTime, EventHandler **eventHandlers, void *extraData) { int numEvents = GetNumEvents(); Event *events = GetEvents(); + EventHandler *eh; int i; if (previousTime < cacheCurrentTime) { i = currentIdx; - if (i > -1 && previousTime < events[i].triggerTime) { - float *eventTime = &events[i].triggerTime; - - do { - i--; - eventTime -= 4; - if (i < 0) { - break; - } - } while (previousTime < *eventTime); + + while (i > -1 && previousTime < events[i].triggerTime) { + i--; } currentIdx = i + 1; } i = currentIdx; - bool validIdx = i < numEvents; - if (validIdx && events[i].triggerTime <= previousTime) { - float *eventTime = &events[i].triggerTime; - - do { - i++; - validIdx = i < numEvents; - eventTime += 4; - if (!validIdx) { - break; - } - } while (*eventTime <= previousTime); + while (i < numEvents && events[i].triggerTime <= previousTime) { + i++; } currentIdx = i; if (previousTime == currentTime) { - if (!validIdx) { + if (numEvents <= i) { currentIdx = numEvents - 1; - i = currentIdx; } - if (i > -1 && currentTime <= events[i].triggerTime) { - float *eventTime = &events[i].triggerTime; + i = currentIdx; - do { - i--; - eventTime -= 4; - if (i < 0) { - break; - } - } while (currentTime <= *eventTime); + while (i > -1 && currentTime <= events[i].triggerTime) { + i--; } - - currentIdx = i + 1; + i++; } else { if (previousTime > currentTime) { - if (validIdx) { - Event *event = &events[i]; - i = numEvents - i; - - do { - EventHandler *eventHandler = eventHandlers[event->eventId]; - if (eventHandler) { - eventHandler->HandleEvent(currentTime, *event, extraData); - } - i--; - event++; - } while (i != 0); - } + for (; i < numEvents; i++) { + eh = eventHandlers[events[i].eventId]; - currentIdx = 0; + if (eh) { + eh->HandleEvent(currentTime, events[i], extraData); + } + } + i = 0; } - i = currentIdx; - validIdx = i < numEvents; - if (validIdx) { - Event *event = &events[i]; - - do { - if (currentTime < event->triggerTime) { - break; - } + currentIdx = i; - EventHandler *eventHandler = eventHandlers[event->eventId]; - if (eventHandler) { - eventHandler->HandleEvent(currentTime, *event, extraData); - } + for (i = currentIdx; i < numEvents; i++) { + if (currentTime < events[i].triggerTime) { + break; + } - i++; - validIdx = i < numEvents; - event++; - } while (validIdx); + eh = eventHandlers[events[i].eventId]; + if (eh) { + eh->HandleEvent(currentTime, events[i], extraData); + } } + } - currentIdx = i; - if (!validIdx) { - currentIdx = numEvents - 1; - } + currentIdx = i; + if (numEvents <= i) { + currentIdx = numEvents - 1; } cacheCurrentTime = currentTime; From 1903d277aacac4e414b64feb588cac948fdadcce Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:20:24 +0100 Subject: [PATCH 332/372] 89.091%: polish RawEventChannel compares Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawEventChannel.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp index 21febaf9f..80449e7d1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp @@ -11,7 +11,7 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId if (previousTime < cacheCurrentTime) { i = currentIdx; - while (i > -1 && previousTime < events[i].triggerTime) { + while (0 <= i && events[i].triggerTime > previousTime) { i--; } currentIdx = i + 1; @@ -23,21 +23,19 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId i++; } - currentIdx = i; - if (previousTime == currentTime) { - if (numEvents <= i) { + if (i >= numEvents) { currentIdx = numEvents - 1; } i = currentIdx; - while (i > -1 && currentTime <= events[i].triggerTime) { + while (0 <= i && events[i].triggerTime >= currentTime) { i--; } i++; } else { - if (previousTime > currentTime) { + if (currentTime < previousTime) { for (; i < numEvents; i++) { eh = eventHandlers[events[i].eventId]; @@ -50,11 +48,7 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId currentIdx = i; - for (i = currentIdx; i < numEvents; i++) { - if (currentTime < events[i].triggerTime) { - break; - } - + for (i = currentIdx; i < numEvents && events[i].triggerTime <= currentTime; i++) { eh = eventHandlers[events[i].eventId]; if (eh) { eh->HandleEvent(currentTime, events[i], extraData); @@ -63,7 +57,7 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId } currentIdx = i; - if (numEvents <= i) { + if (i >= numEvents) { currentIdx = numEvents - 1; } From 7e3a8c493041787c7f306d26801e48cecd13413c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:22:07 +0100 Subject: [PATCH 333/372] 89.101%: merge RawEventChannel replay path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawEventChannel.cpp | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp index 80449e7d1..cf2f971fb 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp @@ -23,6 +23,8 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId i++; } + currentIdx = i; + if (previousTime == currentTime) { if (i >= numEvents) { currentIdx = numEvents - 1; @@ -33,7 +35,7 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId while (0 <= i && events[i].triggerTime >= currentTime) { i--; } - i++; + currentIdx = i + 1; } else { if (currentTime < previousTime) { for (; i < numEvents; i++) { @@ -43,17 +45,25 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId eh->HandleEvent(currentTime, events[i], extraData); } } - i = 0; + currentIdx = 0; } + } + + i = currentIdx; - currentIdx = i; + while (true) { + if (i >= numEvents) { + break; + } + if (events[i].triggerTime > currentTime) { + break; + } - for (i = currentIdx; i < numEvents && events[i].triggerTime <= currentTime; i++) { - eh = eventHandlers[events[i].eventId]; - if (eh) { - eh->HandleEvent(currentTime, events[i], extraData); - } + eh = eventHandlers[events[i].eventId]; + if (eh) { + eh->HandleEvent(currentTime, events[i], extraData); } + i++; } currentIdx = i; From b4271ceae81a134e19391cebddb57d9c3dc359b4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:31:42 +0100 Subject: [PATCH 334/372] 89.127%: improve FnDeltaQFast masked setup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h | 2 +- .../Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 57 ++++++++++++++----- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h index a2dfb7f93..6e8afbbf7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h @@ -83,7 +83,7 @@ struct DeltaQFast : public AnimMemoryMap { return 1 << mBinLengthPower; } - unsigned char GetBinLengthPower() const { + unsigned int GetBinLengthPower() const { return mBinLengthPower; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index f03c7ce0a..78a3184ee 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -571,21 +571,48 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM } DeltaQFast *deltaQ = reinterpret_cast(mpAnim); - float *quatBase = sqt + 4; - unsigned char numBones = deltaQ->mNumBones; + float *q = sqt + 4; unsigned char *boneIdxs = deltaQ->mBoneIdxs; - if (!numBones) { + if (!deltaQ->mNumBones) { return true; } - int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex = mPrevKey < 1 ? 0 : mPrevKey - 1; + + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); int prevBinIdx = mPrevKey >> deltaQ->GetBinLengthPower(); bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); - unsigned char *binData = GetQFastBin(mBins, mBinSize, floorBinIdx); - DeltaQFastPhysical *floorPhys = GetQFastPhysical(binData); + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; + DeltaQFastPhysical *floorPhys = reinterpret_cast(binData); int prevDeltaIdx; if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { @@ -633,14 +660,18 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM } else if (floorKey == 0) { slerpReqd = currTime != 0.0f; if (slerpReqd) { - t = currTime / static_cast(deltaQ->mTimes[0]); + float ceilKeyTime = static_cast(deltaQ->mTimes[0]); + + t = currTime / ceilKeyTime; } } else { - float floorTimef = static_cast(deltaQ->mTimes[floorKey - 1]); + float floorKeyTime = static_cast(deltaQ->mTimes[floorKey - 1]); - slerpReqd = currTime != floorTimef; + slerpReqd = currTime != floorKeyTime; if (slerpReqd) { - t = (currTime - floorTimef) / (static_cast(deltaQ->mTimes[floorKey]) - floorTimef); + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + + t = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); } } @@ -656,7 +687,7 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM float *prevQ = reinterpret_cast(&mPrevQs[ibone]); float *nextQ = reinterpret_cast(&mNextQs[ibone]); - float *out = &quatBase[boneIdx * 12]; + float *out = &q[boneIdx * 12]; float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; @@ -676,7 +707,7 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM continue; } - float *out = &quatBase[boneIdx * 12]; + float *out = &q[boneIdx * 12]; out[0] = mPrevQs[ibone].x; out[1] = mPrevQs[ibone].y; @@ -693,7 +724,7 @@ bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneM } DeltaQFastPhysical *physical = &mConstPhysical[ibone]; - float *out = &quatBase[boneIdx * 12]; + float *out = &q[boneIdx * 12]; out[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; out[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; From 49474789e489d63a7a847bc350367015ecbab464 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 21:54:07 +0100 Subject: [PATCH 335/372] 89.148%: align FnEventBlender branch flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnEventBlender.cpp | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index 7a082df7b..b5b6b9539 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -4,24 +4,22 @@ namespace EAGL4Anim { void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { - FnAnim *anim = mAnim[0]; - float timeOffset = mTimeOffset[0]; - if (currentTime > mStartTransTime) { - if (currentTime >= mEndTransTime) { - anim = mAnim[1]; - timeOffset = mTimeOffset[1]; - } else if (mTriggerType == BOTH) { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - anim = mAnim[1]; - timeOffset = mTimeOffset[1]; - } else if (mTriggerType == SECOND_ONLY) { - anim = mAnim[1]; - timeOffset = mTimeOffset[1]; + if (currentTime < mEndTransTime) { + if (mTriggerType == FIRST_ONLY) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + } else if (mTriggerType == SECOND_ONLY) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + } else { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + } + return; } + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + } else { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); } - - anim->Eval(previousTime - timeOffset, currentTime - timeOffset, data); } }; // namespace EAGL4Anim From 93f33d5334146b6854ae4288f0ac32114ff9392c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:01:13 +0100 Subject: [PATCH 336/372] 89.335%: move raw pose helpers inline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp | 136 ------------------ .../Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 7 +- .../Indep/Src/EAGL4Anim/RawPoseChannel.h | 53 +++++++ 3 files changed, 54 insertions(+), 142 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp index ff8f0ade3..f6786a9e5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp @@ -520,140 +520,4 @@ bool FnKeyQuatChan::EvalSQTMask(float currTime, float *sqt, const BoneMask *bone return true; } -// TODO move -float qt0[7]; - -// TODO move -void QuatF4(float *&data, float *output) { - output[0] = *data++; - output[1] = *data++; - output[2] = *data++; - output[3] = *data++; -} - -// TODO inline and move -void EulF3(float *&data, float *output) { - const float degreesToRadians = 0.017453294f; - float x = *data++ * degreesToRadians * 0.5f; - float y = *data++ * degreesToRadians * 0.5f; - float z = *data++ * degreesToRadians * 0.5f; - float cx = cosf(x); - float cy = cosf(y); - float cz = cosf(z); - float sx = sinf(x); - float sy = sinf(y); - float sz = sinf(z); - - output[0] = cy * sx * cz - sy * cx * sz; - output[2] = cy * cx * sz - sy * sx * cz; - output[1] = cy * sx * sz + sy * cx * cz; - output[3] = cy * cx * cz + sy * sx * sz; -} - -// TODO inline and move -void TranF3(float *&data, float *output) { - output[4] = *data++; - output[5] = *data++; - output[6] = *data++; -} - -// TODO inline and move -void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { - qt0[0] = *data0++; - qt0[1] = *data0++; - qt0[2] = *data0++; - qt0[3] = *data0++; - - output[0] = *data1++; - output[1] = *data1++; - output[2] = *data1++; - output[3] = *data1++; - - if (qt0[0] * output[0] + qt0[1] * output[1] + qt0[2] * output[2] + qt0[3] * output[3] > 0.0f) { - output[0] = w * (output[0] - qt0[0]) + qt0[0]; - output[1] = w * (output[1] - qt0[1]) + qt0[1]; - output[2] = w * (output[2] - qt0[2]) + qt0[2]; - output[3] = w * (output[3] - qt0[3]) + qt0[3]; - } else { - output[0] = qt0[0] - w * (output[0] + qt0[0]); - output[1] = qt0[1] - w * (output[1] + qt0[1]); - output[2] = qt0[2] - w * (output[2] + qt0[2]); - output[3] = qt0[3] - w * (output[3] + qt0[3]); - } - - float invNorm = 1.0f / sqrtf(output[0] * output[0] + output[1] * output[1] + output[2] * output[2] + output[3] * output[3]); - - output[0] = output[0] * invNorm; - output[3] = output[3] * invNorm; - output[1] = output[1] * invNorm; - output[2] = output[2] * invNorm; -} - -// TODO inline and move -void EulF3Interp(float w, float *&data0, float *&data1, float *output) { - const float degreesToRadians = 0.017453294f; - - float x = *data0++ * degreesToRadians * 0.5f; - float y = *data0++ * degreesToRadians * 0.5f; - float z = *data0++ * degreesToRadians * 0.5f; - float cx = cosf(x); - float cy = cosf(y); - float cz = cosf(z); - float sx = sinf(x); - float sy = sinf(y); - float sz = sinf(z); - - qt0[0] = cy * sx * cz - sy * cx * sz; - qt0[2] = cy * cx * sz - sy * sx * cz; - qt0[1] = cy * sx * sz + sy * cx * cz; - qt0[3] = cy * cx * cz + sy * sx * sz; - - x = *data1++ * degreesToRadians * 0.5f; - y = *data1++ * degreesToRadians * 0.5f; - z = *data1++ * degreesToRadians * 0.5f; - cx = cosf(x); - cy = cosf(y); - cz = cosf(z); - sx = sinf(x); - sy = sinf(y); - sz = sinf(z); - - output[0] = cy * sx * cz - sy * cx * sz; - output[2] = cy * cx * sz - sy * sx * cz; - output[1] = cy * sx * sz + sy * cx * cz; - output[3] = cy * cx * cz + sy * sx * sz; - - if (qt0[0] * output[0] + qt0[1] * output[1] + qt0[2] * output[2] + qt0[3] * output[3] > 0.0f) { - output[0] = w * (output[0] - qt0[0]) + qt0[0]; - output[1] = w * (output[1] - qt0[1]) + qt0[1]; - output[2] = w * (output[2] - qt0[2]) + qt0[2]; - output[3] = w * (output[3] - qt0[3]) + qt0[3]; - } else { - output[0] = qt0[0] - w * (output[0] + qt0[0]); - output[1] = qt0[1] - w * (output[1] + qt0[1]); - output[2] = qt0[2] - w * (output[2] + qt0[2]); - output[3] = qt0[3] - w * (output[3] + qt0[3]); - } - - float invNorm = 1.0f / sqrtf(output[0] * output[0] + output[1] * output[1] + output[2] * output[2] + output[3] * output[3]); - - output[0] = output[0] * invNorm; - output[3] = output[3] * invNorm; - output[1] = output[1] * invNorm; - output[2] = output[2] * invNorm; -} - -// TODO inline and move -void TranF3Interp(float w, float *&data0, float *&data1, float *output) { - qt0[4] = *data0++; - qt0[5] = *data0++; - qt0[6] = *data0++; - output[4] = *data1++; - output[5] = *data1++; - output[6] = *data1++; - output[4] = qt0[4] + w * (output[4] - qt0[4]); - output[5] = qt0[5] + w * (output[5] - qt0[5]); - output[6] = qt0[6] + w * (output[6] - qt0[6]); -} - }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index 4ae44213e..d46a712ad 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -4,12 +4,7 @@ namespace EAGL4Anim { -void QuatF4(float *&data, float *output); -void EulF3(float *&data, float *output); -void TranF3(float *&data, float *output); -void QuatF4Interp(float w, float *&data0, float *&data1, float *output); -void EulF3Interp(float w, float *&data0, float *&data1, float *output); -void TranF3Interp(float w, float *&data0, float *&data1, float *output); +float qt0[7]; void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { RawPoseChannel *rawPoseChannel = reinterpret_cast(anim); diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h index d2f4c596e..2d529990f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h @@ -5,11 +5,64 @@ #pragma once #endif +#include "AnimUtil.h" #include "AnimMemoryMap.h" #include "BoneMask.h" namespace EAGL4Anim { +extern float qt0[7]; + +inline float DegToRad(float deg) { + return deg * 0.017453294f; +} + +inline void QuatF4(float *&data, float *output) { + output[0] = *data++; + output[1] = *data++; + output[2] = *data++; + output[3] = *data++; +} + +inline void EulF3(float *&data, float *output) { + float eulData[3]; + + eulData[0] = DegToRad(*data++); + eulData[1] = DegToRad(*data++); + eulData[2] = DegToRad(*data++); + EulToQuat(eulData, output); +} + +inline void TranF3(float *&data, float *output) { + output[4] = *data++; + output[5] = *data++; + output[6] = *data++; +} + +inline void QuatBlendF4(float w, const float *d0, const float *d1, float *out) { + FastQuatBlendF4(w, d0, d1, out); +} + +inline void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { + QuatF4(data0, qt0); + QuatF4(data1, output); + QuatBlendF4(w, qt0, output, output); +} + +inline void EulF3Interp(float w, float *&data0, float *&data1, float *output) { + EulF3(data0, qt0); + EulF3(data1, output); + QuatBlendF4(w, qt0, output, output); +} + +inline void TranF3Interp(float w, float *&data0, float *&data1, float *output) { + TranF3(data0, qt0); + TranF3(data1, output); + output[4] = qt0[4] + w * (output[4] - qt0[4]); + output[5] = qt0[5] + w * (output[5] - qt0[5]); + output[6] = qt0[6] + w * (output[6] - qt0[6]); +} + // total size: 0x10 class RawPoseChannel : public AnimMemoryMap { public: From 4c45d92e5e13712ca59b3ce01426150027027003 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:19:35 +0100 Subject: [PATCH 337/372] 89.468%: improve FnPoseBlender::Blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index ee39d6866..4d0d936b8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -51,19 +51,31 @@ void FnPoseBlender::operator delete(void *ptr, size_t size) { void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float *pose1, float *result, const BoneMask *boneMask) { if (!boneMask) { - for (int boneIdx = 0, poseIdx = 0; boneIdx < numBones; ++boneIdx, poseIdx += 12) { - FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); - result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); - result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); - result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); + int q = 0; + + for (int boneIdx = 0; boneIdx < numBones; ++boneIdx) { + q += 4; + FastQuatBlendF4(w, &pose0[q], &pose1[q], &result[q]); + q += 4; + result[q + 0] = pose0[q + 0] + w * (pose1[q + 0] - pose0[q + 0]); + result[q + 1] = pose0[q + 1] + w * (pose1[q + 1] - pose0[q + 1]); + result[q + 2] = pose0[q + 2] + w * (pose1[q + 2] - pose0[q + 2]); + q += 4; } } else { - for (int boneIdx = 0, poseIdx = 0; boneIdx < numBones; ++boneIdx, poseIdx += 12) { + int q = 0; + + for (int boneIdx = 0; boneIdx < numBones; ++boneIdx) { + q += 4; if (boneMask->GetBone(boneIdx)) { - FastQuatBlendF4(w, &pose0[poseIdx + 4], &pose1[poseIdx + 4], &result[poseIdx + 4]); - result[poseIdx + 8] = pose0[poseIdx + 8] + w * (pose1[poseIdx + 8] - pose0[poseIdx + 8]); - result[poseIdx + 9] = pose0[poseIdx + 9] + w * (pose1[poseIdx + 9] - pose0[poseIdx + 9]); - result[poseIdx + 10] = pose0[poseIdx + 10] + w * (pose1[poseIdx + 10] - pose0[poseIdx + 10]); + FastQuatBlendF4(w, &pose0[q], &pose1[q], &result[q]); + q += 4; + result[q + 0] = pose0[q + 0] + w * (pose1[q + 0] - pose0[q + 0]); + result[q + 1] = pose0[q + 1] + w * (pose1[q + 1] - pose0[q + 1]); + result[q + 2] = pose0[q + 2] + w * (pose1[q + 2] - pose0[q + 2]); + q += 4; + } else { + q += 8; } } } From 50115df79925edaa359fb4b6de779cabbc0cf420 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:30:53 +0100 Subject: [PATCH 338/372] 89.481%: improve RawPoseChannel eval order Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index d46a712ad..88b3c1156 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -76,9 +76,9 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const if (t != 0.0f && interp) { int *sig = GetInterpSig(); - int *sigEnd = sig + mSigSize; float *data0 = GetFrame(frame); float *data1 = GetFrame(frame + 1); + int *sigEnd = sig + mSigSize; if (!boneMask) { while (sig < sigEnd) { @@ -127,8 +127,8 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const void RawPoseChannel::EvalFrame(int frame, float *outputPose, const BoneMask *boneMask) { int *sig = GetNonInterpSig(); - int *sigEnd = sig + mSigSize; float *frameData = GetFrame(frame); + int *sigEnd = sig + mSigSize; if (!boneMask) { while (sig < sigEnd) { From 9f833e580f470683baf5385717128b1a34521812 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:31:53 +0100 Subject: [PATCH 339/372] 89.484%: improve FnEventBlender branch layout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnEventBlender.cpp | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index b5b6b9539..f0003b4ac 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -4,21 +4,19 @@ namespace EAGL4Anim { void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { - if (currentTime > mStartTransTime) { - if (currentTime < mEndTransTime) { - if (mTriggerType == FIRST_ONLY) { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - } else if (mTriggerType == SECOND_ONLY) { - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); - } else { + if (currentTime <= mStartTransTime) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + } else if (currentTime >= mEndTransTime) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + } else { + if (mTriggerType == FIRST_ONLY) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + } else { + if (mTriggerType != SECOND_ONLY) { mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); } - return; + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); } - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); - } else { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); } } From fab426b829d4142cddc9a0335e6a41039407b6a4 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:41:49 +0100 Subject: [PATCH 340/372] 89.550%: improve run blender facing blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 9ec63ec74..45a8682bc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -290,17 +290,14 @@ bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { } bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { - if (!f) { - return false; - } - float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); UMath::Vector4 q0; + UMath::Vector4 q1; UMath::Vector4 q; UMath::Vector4 xAxis; UMath::Vector4 xAxis1; - if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, buffer, 0)) { + if (!mFnAnims[0]->EvalSQT(t0, buffer, 0)) { return false; } @@ -308,11 +305,11 @@ bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { if (mWeight != 0.0f) { mSkeleton->GetStillPose(buffer, 0); - if (!mFnAnims[1] || !mFnAnims[1]->EvalSQT(t1, buffer, 0)) { + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { return false; } - UMath::Vector4 q1 = *reinterpret_cast(&buffer[4]); + q1 = *reinterpret_cast(&buffer[4]); FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); } else { q = q0; @@ -320,9 +317,8 @@ bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { xAxis = kFacingAxis; QuatTransformPoint(q, xAxis, xAxis1); - f[0] = xAxis1.x; f[1] = xAxis1.z; - printf("Facing: %g %g\n", f[0], f[1]); + f[0] = xAxis1.x; return true; } From 85ef7989f902426ab256a7ade18d86bdb4b23bf1 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:51:57 +0100 Subject: [PATCH 341/372] 89.625%: match ComputeRootQ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 45a8682bc..ecf31f72f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -338,27 +338,27 @@ void FnRunBlender::ComputeEndRootQ(UMath::Vector4 &q) const { } void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { - ScratchBuffer &scratch = ScratchBuffer::GetScratchBuffer(0); - float *pose = reinterpret_cast(scratch.GetBuffer()); + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); + UMath::Vector4 q0; + UMath::Vector4 q1; - if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, pose, 0)) { + if (!mFnAnims[0]->EvalSQT(t0, buffer, 0)) { return; } - UMath::Vector4 q0 = *reinterpret_cast(&pose[4]); + q0 = *reinterpret_cast(&buffer[4]); - if (mWeight == 0.0f) { - q = q0; - return; - } + if (mWeight != 0.0f) { + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { + return; + } - mSkeleton->GetStillPose(pose, 0); - if (!mFnAnims[1] || !mFnAnims[1]->EvalSQT(t1, pose, 0)) { - return; + q1 = *reinterpret_cast(&buffer[4]); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + } else { + q = q0; } - - UMath::Vector4 q1 = *reinterpret_cast(&pose[4]); - FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); } float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { From c1d0360d95916a8e626d1a133498160e95056fbc Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:55:17 +0100 Subject: [PATCH 342/372] 89.649%: improve FnTurnBlender EvalSQT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 632bba08c..700da1ffa 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -48,27 +48,27 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo } currTime += mOffset; - int cycleIdx = ComputeCycleIdx(currTime, 0.0f, 2.0f / mFreq); + int cIdx = ComputeCycleIdx(currTime, 0.0f, 2.0f / mFreq); float t0 = CycleTime(mFreq * mCycles[0] * currTime, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; float t1 = CycleTime(mFreq * mCycles[1] * currTime, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; mSkeleton->GetStillPose(sqtBuffer, 0); - if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { + if (!mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { return false; } - if (mWeight != 0.0f && mFnAnims[1]) { - float *blendPose = reinterpret_cast(ScratchBuffer::GetScratchBuffer(1).GetBuffer()); + if (mWeight != 0.0f) { + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(1).GetBuffer()); - mSkeleton->GetStillPose(blendPose, 0); - if (!mFnAnims[1]->EvalSQT(t1, blendPose, 0)) { + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { return false; } - FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, nullptr); + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, buffer, sqtBuffer, nullptr); } - AlignCycleBeginEnd(cycleIdx); + AlignCycleBeginEnd(cIdx); AlignRootQ(sqtBuffer); return true; } From 2fe1e462d721adab861c300bfa2df9c2c913981c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:57:00 +0100 Subject: [PATCH 343/372] 89.699%: match FnRunBlender EvalSQT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index ecf31f72f..7a4282975 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -109,29 +109,29 @@ bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bon } currTime += mOffset; - float evalTime0 = mFreq * mCycles[0] * currTime + mAlignFrame[0]; - float evalTime1 = mFreq * mCycles[1] * currTime + mAlignFrame[1]; - int cycleIdx = ComputeCycleIdx(evalTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); - float t0 = CycleTime(evalTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); - float t1 = CycleTime(evalTime1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); + float t0 = mFreq * mCycles[0] * currTime + mAlignFrame[0]; + float t1 = mFreq * mCycles[1] * currTime + mAlignFrame[1]; + int cIdx = ComputeCycleIdx(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + t0 = CycleTime(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + t1 = CycleTime(t1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); mSkeleton->GetStillPose(sqtBuffer, 0); - if (!mFnAnims[0] || !mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { + if (!mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { return false; } - if (mWeight != 0.0f && mFnAnims[1]) { - float *blendPose = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); + if (mWeight != 0.0f) { + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); - mSkeleton->GetStillPose(blendPose, 0); - if (!mFnAnims[1]->EvalSQT(t1, blendPose, 0)) { + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { return false; } - FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, blendPose, sqtBuffer, nullptr); + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, buffer, sqtBuffer, nullptr); } - AlignCycleBeginEnd(cycleIdx); + AlignCycleBeginEnd(cIdx); AlignRootQ(sqtBuffer); return true; } From 991587b7d7dfcc1e90d2dceb51fa8ec4b3f5aa69 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 22:57:41 +0100 Subject: [PATCH 344/372] 89.732%: match FnTurnBlender EvalSQT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 700da1ffa..070d04c31 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -49,8 +49,11 @@ bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *bo currTime += mOffset; int cIdx = ComputeCycleIdx(currTime, 0.0f, 2.0f / mFreq); - float t0 = CycleTime(mFreq * mCycles[0] * currTime, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; - float t1 = CycleTime(mFreq * mCycles[1] * currTime, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; + float t0 = mFreq * mCycles[0] * currTime; + float t1 = mFreq * mCycles[1] * currTime; + + t0 = CycleTime(t0, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; + t1 = CycleTime(t1, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; mSkeleton->GetStillPose(sqtBuffer, 0); if (!mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { From f2dd138d3f771176d13fc23b84a85cd2d382ebb5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 23:00:35 +0100 Subject: [PATCH 345/372] 89.818%: match FnRunBlender EvalVel2D Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 7a4282975..26fa53656 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -228,27 +228,33 @@ bool FnRunBlender::EvalPhase(float currTime, PhaseValue &phase) { bool FnRunBlender::EvalVel2D(float currTime, float *vel) { mPrevTime = currTime; - if (!mVels) { - return false; - } - if (!mFnVelAnims[0]) { - SetWeight(0.0f); - } + if (mVels) { + if (!mFnVelAnims[0]) { + SetWeight(0.0f); + } + + { + float t0; + float t1; + int cIdx; - float evalTime = currTime + mOffset; - float cycleTime0 = mFreq * mCycles[0] * evalTime + mAlignFrame[0]; - int cycleIdx = ComputeCycleIdx(cycleTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); - float t0 = CycleTime(cycleTime0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); - float t1 = CycleTime( - mFreq * mCycles[1] * evalTime + mAlignFrame[1], - 0.0f, - static_cast(mPhases[mIdx + 1]->mNumFrames - 1) - ); - - if (BlendVel(t0, t1, vel)) { - AlignCycleBeginEnd(cycleIdx); - AlignVel(vel); - return true; + currTime += mOffset; + + t0 = mFreq * mCycles[0] * currTime + mAlignFrame[0]; + t1 = mFreq * mCycles[1] * currTime + mAlignFrame[1]; + cIdx = ComputeCycleIdx(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + + t0 = CycleTime(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + t1 = CycleTime(t1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); + + if (!BlendVel(t0, t1, vel)) { + return false; + } + + AlignCycleBeginEnd(cIdx); + AlignVel(vel); + return true; + } } return false; From dae380e39822574ee12fc20124b2139b70155b5c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 23:05:48 +0100 Subject: [PATCH 346/372] 89.832%: match FnPoseBlender Blend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnPoseBlender.cpp | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index 4d0d936b8..2566dd1cc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -50,32 +50,31 @@ void FnPoseBlender::operator delete(void *ptr, size_t size) { void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float *pose1, float *result, const BoneMask *boneMask) { + int i; + int j; + if (!boneMask) { - int q = 0; - - for (int boneIdx = 0; boneIdx < numBones; ++boneIdx) { - q += 4; - FastQuatBlendF4(w, &pose0[q], &pose1[q], &result[q]); - q += 4; - result[q + 0] = pose0[q + 0] + w * (pose1[q + 0] - pose0[q + 0]); - result[q + 1] = pose0[q + 1] + w * (pose1[q + 1] - pose0[q + 1]); - result[q + 2] = pose0[q + 2] + w * (pose1[q + 2] - pose0[q + 2]); - q += 4; + j = 0; + + for (i = 0; i < numBones; ++i) { + j += 4; + FastQuatBlendF4(w, &pose0[j], &pose1[j], &result[j]); + j += 4; + ::LinearBlendF3(w, &pose0[j], &pose1[j], &result[j]); + j += 4; } } else { - int q = 0; - - for (int boneIdx = 0; boneIdx < numBones; ++boneIdx) { - q += 4; - if (boneMask->GetBone(boneIdx)) { - FastQuatBlendF4(w, &pose0[q], &pose1[q], &result[q]); - q += 4; - result[q + 0] = pose0[q + 0] + w * (pose1[q + 0] - pose0[q + 0]); - result[q + 1] = pose0[q + 1] + w * (pose1[q + 1] - pose0[q + 1]); - result[q + 2] = pose0[q + 2] + w * (pose1[q + 2] - pose0[q + 2]); - q += 4; + j = 0; + + for (i = 0; i < numBones; ++i) { + j += 4; + if (boneMask->GetBone(i)) { + FastQuatBlendF4(w, &pose0[j], &pose1[j], &result[j]); + j += 4; + ::LinearBlendF3(w, &pose0[j], &pose1[j], &result[j]); + j += 4; } else { - q += 8; + j += 8; } } } From a0e679d0291cb5ad4fffee8a4b8501b074f42dbf Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 23:21:22 +0100 Subject: [PATCH 347/372] 89.882%: match FnRunBlender BlendFacing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 26fa53656..3406ffa9c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -45,10 +45,10 @@ static inline void QuatTransformPoint(const UMath::Vector4 &q, const UMath::Vect yy = q.y * ys; yz = q.y * zs; zz = q.z * zs; - result.x = p.x * (p.w - (yy + zz)) + p.y * (xy - wz) + p.z * (xz + wy); - result.y = p.x * (xy + wz) + p.y * (p.w - (xx + zz)) + p.z * (yz - wx); - result.z = p.x * (xz - wy) + p.y * (yz + wx) + p.z * (p.w - (xx + yy)); - result.w = p.w; + result.x = p.x * (1.0f - (yy + zz)) + p.y * (xy - wz) + p.z * (xz + wy); + result.y = p.x * (xy + wz) + p.y * (1.0f - (xx + zz)) + p.z * (yz - wx); + result.z = p.x * (xz - wy) + p.y * (yz + wx) + p.z * (1.0f - (xx + yy)); + result.w = 1.0f; } FnRunBlender::FnRunBlender() From c6960abfb367ffb44f916f6dc3f6353c876b735b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 23:30:10 +0100 Subject: [PATCH 348/372] 89.891%: match Run and Turn CycleTime Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 8 +++----- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 3406ffa9c..df3bdfcba 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -368,23 +368,21 @@ void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { } float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { - float len = endTime - startTime; float tmp; int n; + float len = endTime - startTime; if (t < startTime) { tmp = startTime - t; n = FloatToInt(tmp / len); - tmp = tmp - static_cast(n) * len; - return endTime - tmp; + return endTime - (tmp - static_cast(n) * len); } if (t < endTime) { return t; } tmp = t - endTime; n = FloatToInt(tmp / len); - tmp = tmp - static_cast(n) * len; - return startTime + tmp; + return startTime + (tmp - static_cast(n) * len); } int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 070d04c31..8c1fed49d 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -192,23 +192,21 @@ float FnTurnBlender::GetFrequency() const { } float FnTurnBlender::CycleTime(float t, float startTime, float endTime) const { - float len = endTime - startTime; float tmp; int n; + float len = endTime - startTime; if (t < startTime) { tmp = startTime - t; n = FloatToInt(tmp / len); - tmp = tmp - static_cast(n) * len; - return endTime - tmp; + return endTime - (tmp - static_cast(n) * len); } if (t < endTime) { return t; } tmp = t - endTime; n = FloatToInt(tmp / len); - tmp = tmp - static_cast(n) * len; - return startTime + tmp; + return startTime + (tmp - static_cast(n) * len); } int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { From 80240d6e13794f9e61e6d0364ace5512192c14af Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 23:54:16 +0100 Subject: [PATCH 349/372] 89.899%: improve FnEventBlender Eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnEventBlender.cpp | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index f0003b4ac..653b0f1e6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -4,19 +4,21 @@ namespace EAGL4Anim { void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { - if (currentTime <= mStartTransTime) { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - } else if (currentTime >= mEndTransTime) { - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); - } else { - if (mTriggerType == FIRST_ONLY) { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - } else { - if (mTriggerType != SECOND_ONLY) { + if (mStartTransTime < currentTime) { + if (currentTime < mEndTransTime) { + if (mTriggerType == FIRST_ONLY) { mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + } else { + if (mTriggerType != SECOND_ONLY) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + } + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); } + } else { mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); } + } else { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); } } From 1ea2bd7c3d1654b92f950b54e1e466d3fa554ce5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Wed, 25 Mar 2026 23:55:57 +0100 Subject: [PATCH 350/372] 89.900%: refine FnEventBlender Eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index 653b0f1e6..03229d747 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -4,7 +4,7 @@ namespace EAGL4Anim { void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { - if (mStartTransTime < currentTime) { + if (currentTime > mStartTransTime) { if (currentTime < mEndTransTime) { if (mTriggerType == FIRST_ONLY) { mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); From 5d692579c10f420e9c092e593aa0be782b3339dd Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 10:14:21 +0100 Subject: [PATCH 351/372] 89.913%: improve turn facing helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 2 +- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 32 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index df3bdfcba..96af25055 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -417,8 +417,8 @@ void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const void FnRunBlender::AlignCycleBeginEnd(int cIdx) { if (!mInit) { - mInit = true; mCycleIdx = -1; + mInit = true; mAlignQ.z = 0.0f; mAlignQ.w = 1.0f; mAlignQ.x = 0.0f; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 8c1fed49d..e6a93bc7a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -241,12 +241,12 @@ void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { if (!mInit) { - mInit = true; mCycleIdx = -1; + mInit = true; + mAlignQ.w = 1.0f; + mAlignQ.z = 0.0f; mAlignQ.x = 0.0f; mAlignQ.y = 0.0f; - mAlignQ.z = 0.0f; - mAlignQ.w = 1.0f; } else if (mCycleIdx != cIdx) { float v0[2]; float v1[2]; @@ -290,17 +290,16 @@ void FnTurnBlender::AlignVel(float *vel) const { bool FnTurnBlender::BlendBeginFacing(float *f) const { FnRunBlender *fnA; - - if (!f) { - return false; - } - UMath::Vector4 q0; UMath::Vector4 q1; UMath::Vector4 q; UMath::Vector4 xAxis; UMath::Vector4 xAxis1; + if (!f) { + return false; + } + fnA = reinterpret_cast(mFnAnims[0]); fnA->ComputeBeginRootQ(q0); fnA = reinterpret_cast(mFnAnims[1]); @@ -308,25 +307,24 @@ bool FnTurnBlender::BlendBeginFacing(float *f) const { FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); xAxis = kFacingAxis; QuatTransformPoint(q, xAxis, xAxis1); - f[0] = xAxis1.x; f[1] = xAxis1.z; - printf("Facing: %g %g\n", f[0], f[1]); + f[0] = xAxis1.x; + printf("Facing: %g %g\n", xAxis1.x, xAxis1.z); return true; } bool FnTurnBlender::BlendEndFacing(float *f) const { FnRunBlender *fnA; - - if (!f) { - return false; - } - UMath::Vector4 q0; UMath::Vector4 q1; UMath::Vector4 q; UMath::Vector4 xAxis; UMath::Vector4 xAxis1; + if (!f) { + return false; + } + fnA = reinterpret_cast(mFnAnims[0]); fnA->ComputeEndRootQ(q0); fnA = reinterpret_cast(mFnAnims[1]); @@ -334,9 +332,9 @@ bool FnTurnBlender::BlendEndFacing(float *f) const { FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); xAxis = kFacingAxis; QuatTransformPoint(q, xAxis, xAxis1); - f[0] = xAxis1.x; f[1] = xAxis1.z; - printf("Facing: %g %g\n", f[0], f[1]); + f[0] = xAxis1.x; + printf("Facing: %g %g\n", xAxis1.x, xAxis1.z); return true; } From f0188eadace358b193cf9232cf61b99e537c3605 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 10:20:40 +0100 Subject: [PATCH 352/372] 89.927%: match UseFPS helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp | 2 +- src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index a4ff7fb2e..904435e35 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -312,7 +312,7 @@ bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *bone void FnStatelessF3::UseFPS(bool u) { mUseFPS = u; - if (mFPS != 0 || !u) { + if (mFPS != 0 || !mUseFPS) { return; } GetAttribute(AttributeId(AttributeId::ID_FPS), mFPS); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index 3efb68353..c35e87aa5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -276,7 +276,7 @@ bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool void FnStatelessQ::UseFPS(bool u) { mUseFPS = u; - if (mFPS != 0 || !u) { + if (mFPS != 0 || !mUseFPS) { return; } GetAttribute(AttributeId(AttributeId::ID_FPS), mFPS); From 3e88febfc7ac9ef14324421c31266e61b6e43d6c Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 10:33:44 +0100 Subject: [PATCH 353/372] 89.970%: improve FnEventBlender Eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnEventBlender.cpp | 34 +++++++++++-------- .../Indep/Src/EAGL4Anim/RawStateChan.cpp | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index 03229d747..7e59ae90e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -4,22 +4,28 @@ namespace EAGL4Anim { void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { - if (currentTime > mStartTransTime) { - if (currentTime < mEndTransTime) { - if (mTriggerType == FIRST_ONLY) { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - } else { - if (mTriggerType != SECOND_ONLY) { - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - } - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); - } - } else { - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); - } - } else { + if (!(currentTime > mStartTransTime)) { mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + return; } + + if (!(currentTime < mEndTransTime)) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + return; + } + + if (mTriggerType == FIRST_ONLY) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + return; + } + + if (mTriggerType == SECOND_ONLY) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + return; + } + + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); } }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index f2c644bdf..477a8d363 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -225,7 +225,7 @@ bool FnRawStateChan::EvalState(float time, State *s) { bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &resultTime) { const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); - unsigned char stateBuffer[84]; + unsigned char stateBuffer[80]; int keyIdx; for (keyIdx = 0; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { From cff1fbf9421b7b0aaa9baea4a0d93389a3146282 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 10:41:47 +0100 Subject: [PATCH 354/372] 89.991%: match FnEventBlender Eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnEventBlender.cpp | 14 ++--- .../Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 54 +++---------------- .../Indep/Src/EAGL4Anim/RawPoseChannel.h | 44 ++++++++++++++- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index 7e59ae90e..163c11f4e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -14,18 +14,18 @@ void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { return; } - if (mTriggerType == FIRST_ONLY) { + switch (mTriggerType) { + case FIRST_ONLY: mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); return; - } - - if (mTriggerType == SECOND_ONLY) { + case SECOND_ONLY: + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + return; + default: + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); return; } - - mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); - mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); } }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index 88b3c1156..f4bc1506b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -71,56 +71,14 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const if (frame >= lastFrame) { EvalFrame(lastFrame, outputPose, boneMask); - } else { - float t = frameTime - static_cast(frame); - - if (t != 0.0f && interp) { - int *sig = GetInterpSig(); - float *data0 = GetFrame(frame); - float *data1 = GetFrame(frame + 1); - int *sigEnd = sig + mSigSize; - - if (!boneMask) { - while (sig < sigEnd) { - int numChannels = *sig++; - float *nextOutputPose = outputPose + 12; - - for (int ichan = 0; ichan < numChannels; ichan++) { - reinterpret_cast(*sig++)(t, data0, data1, - outputPose + 4); - } - outputPose = nextOutputPose; - } + } else { + float t = frameTime - static_cast(frame); + + if (t != 0.0f && interp) { + EvalInterpFrame(t, frame, frame + 1, outputPose, boneMask); } else { - for (unsigned int ibone = 0; sig < sigEnd; ibone++) { - int numChannels = *sig++; - - if (boneMask->GetBone(ibone)) { - for (int ichan = 0; ichan < numChannels; ichan++) { - reinterpret_cast(*sig++)( - t, data0, data1, outputPose + 4); - } - } else { - for (int ichan = 0; ichan < numChannels; ichan++) { - void (*func)(float, float *&, float *&, float *) = - reinterpret_cast(*sig++); - - if (func == EulF3Interp || func == TranF3Interp) { - data0 += 3; - data1 += 3; - } else if (func == QuatF4Interp) { - data0 += 4; - data1 += 4; - } - } - } - - outputPose += 12; - } + EvalFrame(frame, outputPose, boneMask); } - } else { - EvalFrame(frame, outputPose, boneMask); - } } } } diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h index 2d529990f..0723d4ff3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h @@ -128,7 +128,49 @@ class RawPoseChannel : public AnimMemoryMap { int GetNumBones() const {} - void EvalInterpFrame(float t, int frame0, int frame1, float *outputPose, const BoneMask *boneMask) {} + void EvalInterpFrame(float t, int frame0, int frame1, float *outputPose, const BoneMask *boneMask) { + int *s = GetInterpSig(); + float *d0 = GetFrame(frame0); + float *d1 = GetFrame(frame1); + int *end = s + mSigSize; + + if (!boneMask) { + while (s < end) { + int count = *s++; + float *out = outputPose + 12; + + for (int j = 0; j < count; j++) { + reinterpret_cast(*s++)(t, d0, d1, outputPose + 4); + } + outputPose = out; + } + } else { + for (int i = 0; s < end; i++) { + int count = *s++; + + if (boneMask->GetBone(i)) { + for (int j = 0; j < count; j++) { + reinterpret_cast(*s++)(t, d0, d1, outputPose + 4); + } + } else { + for (int j = 0; j < count; j++) { + void (*func)(float, float *&, float *&, float *) = + reinterpret_cast(*s++); + + if (func == EulF3Interp || func == TranF3Interp) { + d0 += 3; + d1 += 3; + } else if (func == QuatF4Interp) { + d0 += 4; + d1 += 4; + } + } + } + + outputPose += 12; + } + } + } static void InitAnimMemoryMap(AnimMemoryMap *anim); From 634ec6a400c7af571d5509b2e6e8e316336e28a9 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 10:53:14 +0100 Subject: [PATCH 355/372] 89.997%: improve RawPoseChannel InitAnimMemoryMap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index f4bc1506b..1dd8753e5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -17,7 +17,10 @@ void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { isig++; for (int ichan = 0; ichan < numChannels; ichan++) { - switch (*sig) { + int t = *sig; + + isig++; + switch (t) { case EUL: *sig = reinterpret_cast(EulF3); break; @@ -42,7 +45,10 @@ void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { isig++; for (int ichan = 0; ichan < numChannels; ichan++) { - switch (*sig) { + int t = *sig; + + isig++; + switch (t) { case EUL: *sig = reinterpret_cast(EulF3Interp); break; From 987b0b78df2335a5b0cb618b4c8c68482b0b80f8 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:05:10 +0100 Subject: [PATCH 356/372] 90.001%: improve RawPoseChannel InitAnimMemoryMap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index 1dd8753e5..c23680739 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -10,13 +10,15 @@ void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { RawPoseChannel *rawPoseChannel = reinterpret_cast(anim); int numSigs = rawPoseChannel->mSigSize; int *sig = rawPoseChannel->GetNonInterpSig(); - int isig = 0; + int ichan; + int numChannels; + while (isig < numSigs) { - int numChannels = *sig++; + numChannels = *sig++; isig++; - for (int ichan = 0; ichan < numChannels; ichan++) { + for (ichan = 0; ichan < numChannels; ichan++) { int t = *sig; isig++; @@ -41,10 +43,10 @@ void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { sig = rawPoseChannel->GetInterpSig(); isig = 0; while (isig < numSigs) { - int numChannels = *sig++; + numChannels = *sig++; isig++; - for (int ichan = 0; ichan < numChannels; ichan++) { + for (ichan = 0; ichan < numChannels; ichan++) { int t = *sig; isig++; From 21a10ea6cb74e903458f69646b16291c97e263f5 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:08:22 +0100 Subject: [PATCH 357/372] 90.001%: match RawPoseChannel InitAnimMemoryMap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 40 +++++++++---------- .../Indep/Src/EAGL4Anim/RawPoseChannel.h | 4 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index c23680739..e6443d8da 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -7,21 +7,21 @@ namespace EAGL4Anim { float qt0[7]; void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { - RawPoseChannel *rawPoseChannel = reinterpret_cast(anim); - int numSigs = rawPoseChannel->mSigSize; - int *sig = rawPoseChannel->GetNonInterpSig(); - int isig = 0; - int ichan; - int numChannels; - - while (isig < numSigs) { - numChannels = *sig++; - isig++; - - for (ichan = 0; ichan < numChannels; ichan++) { + RawPoseChannel *rawChan = reinterpret_cast(anim); + int i = 0; + int j; + int count; + int *sig = rawChan->GetNonInterpSig(); + int sigSize = rawChan->GetSigSize(); + + while (i < sigSize) { + count = *sig++; + i++; + + for (j = 0; j < count; j++) { int t = *sig; - isig++; + i++; switch (t) { case EUL: *sig = reinterpret_cast(EulF3); @@ -40,16 +40,16 @@ void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { } } - sig = rawPoseChannel->GetInterpSig(); - isig = 0; - while (isig < numSigs) { - numChannels = *sig++; - isig++; + sig = rawChan->GetInterpSig(); + i = 0; + while (i < sigSize) { + count = *sig++; + i++; - for (ichan = 0; ichan < numChannels; ichan++) { + for (j = 0; j < count; j++) { int t = *sig; - isig++; + i++; switch (t) { case EUL: *sig = reinterpret_cast(EulF3Interp); diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h index 0723d4ff3..295d0b501 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h @@ -99,11 +99,11 @@ class RawPoseChannel : public AnimMemoryMap { } int *GetInterpSig() { - return GetNonInterpSig() + mSigSize; + return GetNonInterpSig() + GetSigSize(); } const int *GetInterpSig() const { - return GetNonInterpSig() + mSigSize; + return GetNonInterpSig() + GetSigSize(); } float *GetAnimData() { From d530f75eda8f913a0acc4df5dd482ad282b30add Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:11:33 +0100 Subject: [PATCH 358/372] 90.004%: match RawPoseChannel EvalFrame Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawPoseChannel.cpp | 44 ++++++++++--------- .../Indep/Src/EAGL4Anim/RawPoseChannel.h | 8 ++-- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index e6443d8da..461761b55 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -92,41 +92,45 @@ void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const } void RawPoseChannel::EvalFrame(int frame, float *outputPose, const BoneMask *boneMask) { - int *sig = GetNonInterpSig(); - float *frameData = GetFrame(frame); - int *sigEnd = sig + mSigSize; + int count; + int *s = GetNonInterpSig(); + float *d = GetFrame(frame); + float *out = outputPose; + int *end = s + GetSigSize(); + void (*func)(float *&, float *); + int j; if (!boneMask) { - while (sig < sigEnd) { - int numChannels = *sig++; - float *nextOutputPose = outputPose + 12; + while (s < end) { + count = *s++; - for (int ichan = 0; ichan < numChannels; ichan++) { - reinterpret_cast(*sig++)(frameData, outputPose + 4); + for (j = 0; j < count; j++) { + func = reinterpret_cast(*s++); + func(d, out + 4); } - outputPose = nextOutputPose; + out += 12; } } else { - for (unsigned int ibone = 0; sig < sigEnd; ibone++) { - int numChannels = *sig++; + for (int i = 0; s < end; i++) { + count = *s++; - if (boneMask->GetBone(ibone)) { - for (int ichan = 0; ichan < numChannels; ichan++) { - reinterpret_cast(*sig++)(frameData, outputPose + 4); + if (boneMask->GetBone(i)) { + for (j = 0; j < count; j++) { + func = reinterpret_cast(*s++); + func(d, out + 4); } } else { - for (int ichan = 0; ichan < numChannels; ichan++) { - void (*func)(float *&, float *) = reinterpret_cast(*sig++); - + for (j = 0; j < count; j++) { + func = reinterpret_cast(*s++); if (func == EulF3 || func == TranF3) { - frameData += 3; + d += 3; } else if (func == QuatF4) { - frameData += 4; + d += 4; } } } - outputPose += 12; + out += 12; } } } diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h index 295d0b501..016e18d70 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h @@ -107,19 +107,19 @@ class RawPoseChannel : public AnimMemoryMap { } float *GetAnimData() { - return reinterpret_cast(GetInterpSig() + mSigSize); + return reinterpret_cast(GetInterpSig() + GetSigSize()); } const float *GetAnimData() const { - return reinterpret_cast(GetInterpSig() + mSigSize); + return reinterpret_cast(GetInterpSig() + GetSigSize()); } float *GetFrame(int i) { - return &GetAnimData()[i * mFrameSize]; + return &GetAnimData()[i * GetFrameSize()]; } const float *GetFrame(int i) const { - return &GetAnimData()[i * mFrameSize]; + return &GetAnimData()[i * GetFrameSize()]; } int GetSize() const {} From 9ba90bbd10842b17a4208df7e506856fee97af9d Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:31:35 +0100 Subject: [PATCH 359/372] 90.004%: improve FnRunBlender AlignCycleBeginEnd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index 96af25055..b9b58a62b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -416,16 +416,17 @@ void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const } void FnRunBlender::AlignCycleBeginEnd(int cIdx) { + float v0[2]; + float v1[2]; + if (!mInit) { mCycleIdx = -1; mInit = true; - mAlignQ.z = 0.0f; - mAlignQ.w = 1.0f; mAlignQ.x = 0.0f; mAlignQ.y = 0.0f; + mAlignQ.w = 1.0f; + mAlignQ.z = 0.0f; } else if (mCycleIdx != cIdx) { - float v0[2]; - float v1[2]; UMath::Vector4 q; UMath::Vector4 resultQ; From 6be8b0b1e463a8a4cc33a769b41c6ef9786d890e Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:35:00 +0100 Subject: [PATCH 360/372] 90.012%: match FnRunBlender AlignCycleBeginEnd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index b9b58a62b..c635ab867 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -421,11 +421,11 @@ void FnRunBlender::AlignCycleBeginEnd(int cIdx) { if (!mInit) { mCycleIdx = -1; - mInit = true; mAlignQ.x = 0.0f; mAlignQ.y = 0.0f; - mAlignQ.w = 1.0f; mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; + mInit = true; } else if (mCycleIdx != cIdx) { UMath::Vector4 q; UMath::Vector4 resultQ; From 3da3c7cfc7be628d8532ef00c59c45986a80f97b Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:43:52 +0100 Subject: [PATCH 361/372] 90.027%: improve FnTurnBlender AlignCycleBeginEnd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index e6a93bc7a..e6b8bde62 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -11,7 +11,7 @@ float turnLength(float *v) { return sqrtf(v[0] * v[0] + v[1] * v[1]); } -static int i_6840; +static int i; namespace EAGL4Anim { @@ -240,6 +240,9 @@ void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const } void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { + float v0[2]; + float v1[2]; + if (!mInit) { mCycleIdx = -1; mInit = true; @@ -248,11 +251,8 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { mAlignQ.x = 0.0f; mAlignQ.y = 0.0f; } else if (mCycleIdx != cIdx) { - float v0[2]; - float v1[2]; UMath::Vector4 q; UMath::Vector4 resultQ; - int i; BlendBeginFacing(v0); BlendEndFacing(v1); @@ -262,10 +262,9 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { } QuatMult(mAlignQ, q, resultQ); - i = i_6840++; mAlignQ = resultQ; mCycleIdx = cIdx; - printf("turn align[%d] Q: %g %g %g %g\n\n", i, mAlignQ.x, mAlignQ.y, mAlignQ.z, mAlignQ.w); + printf("turn align[%d] Q: %g %g %g %g\n\n", i++, mAlignQ.x, mAlignQ.y, mAlignQ.z, mAlignQ.w); } } From f64e15c89d14c8f99eaa92f22a7bc44d4f46c940 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:55:30 +0100 Subject: [PATCH 362/372] 90.028%: refine FnTurnBlender AlignCycleBeginEnd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index e6b8bde62..e2f2b0d78 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -11,9 +11,6 @@ float turnLength(float *v) { return sqrtf(v[0] * v[0] + v[1] * v[1]); } -static int i; - - namespace EAGL4Anim { FnTurnBlender::FnTurnBlender() @@ -244,15 +241,16 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { float v1[2]; if (!mInit) { - mCycleIdx = -1; - mInit = true; - mAlignQ.w = 1.0f; - mAlignQ.z = 0.0f; - mAlignQ.x = 0.0f; - mAlignQ.y = 0.0f; +mAlignQ.x = 0.0f; +mAlignQ.y = 0.0f; +mCycleIdx = -1; +mAlignQ.z = 0.0f; +mAlignQ.w = 1.0f; +mInit = true; } else if (mCycleIdx != cIdx) { UMath::Vector4 q; UMath::Vector4 resultQ; + static int i; BlendBeginFacing(v0); BlendEndFacing(v1); From f287f569d08641a71d77356529742c5bae26ef57 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 11:56:11 +0100 Subject: [PATCH 363/372] 90.035%: match FnTurnBlender AlignCycleBeginEnd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index e2f2b0d78..0a655b2d1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -241,12 +241,12 @@ void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { float v1[2]; if (!mInit) { -mAlignQ.x = 0.0f; -mAlignQ.y = 0.0f; -mCycleIdx = -1; -mAlignQ.z = 0.0f; -mAlignQ.w = 1.0f; -mInit = true; + mCycleIdx = -1; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; + mInit = true; } else if (mCycleIdx != cIdx) { UMath::Vector4 q; UMath::Vector4 resultQ; From 810ffc5987884d38569adb32e07fbc3b04848b06 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 12:01:36 +0100 Subject: [PATCH 364/372] 90.041%: match BlendVel helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/FnRunBlender.cpp | 32 +++++++++---------- .../Indep/Src/EAGL4Anim/FnTurnBlender.cpp | 32 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index c635ab867..88b4b1b3a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -261,35 +261,35 @@ bool FnRunBlender::EvalVel2D(float currTime, float *vel) { } bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { - float vel0[2]; - float vel1[2]; + float v0[2]; + float v1[2]; - if (!mFnVelAnims[0]->EvalVel2D(t0, vel0)) { + if (!mFnVelAnims[0]->EvalVel2D(t0, v0)) { return false; } if (mWeight != 0.0f) { - if (!mFnVelAnims[1]->EvalVel2D(t1, vel1)) { + if (!mFnVelAnims[1]->EvalVel2D(t1, v1)) { return false; } - float weight1 = 1.0f - mWeight; + float len; + float w; - vel[0] = mWeight * vel1[0] + weight1 * vel0[0]; - vel[1] = mWeight * vel1[1] + weight1 * vel0[1]; + w = 1.0f - mWeight; - float velLength = length(vel); + vel[0] = mWeight * v1[0] + w * v0[0]; + vel[1] = mWeight * v1[1] + w * v0[1]; - if (velLength != 0.0f) { - float vel1Length = length(vel1); - float vel0Length = length(vel0); - float scale = (mWeight * vel1Length + weight1 * vel0Length) / velLength; + len = length(vel); - vel[0] = vel[0] * scale; - vel[1] = vel[1] * scale; + if (len != 0.0f) { + len = (mWeight * length(v1) + w * length(v0)) / len; + vel[0] = vel[0] * len; + vel[1] = vel[1] * len; } } else { - vel[1] = vel0[1]; - vel[0] = vel0[0]; + vel[1] = v0[1]; + vel[0] = v0[0]; } return true; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index 0a655b2d1..1e035ad9b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -150,35 +150,35 @@ bool FnTurnBlender::EvalVel2D(float currTime, float *vel) { } bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { - float vel0[2]; - float vel1[2]; + float v0[2]; + float v1[2]; - if (!mFnAnims[0]->EvalVel2D(t0, vel0)) { + if (!mFnAnims[0]->EvalVel2D(t0, v0)) { return false; } if (mWeight != 0.0f) { - if (!mFnAnims[1]->EvalVel2D(t1, vel1)) { + if (!mFnAnims[1]->EvalVel2D(t1, v1)) { return false; } - float weight1 = 1.0f - mWeight; + float len; + float w; - vel[0] = mWeight * vel1[0] + weight1 * vel0[0]; - vel[1] = mWeight * vel1[1] + weight1 * vel0[1]; + w = 1.0f - mWeight; - float velLength = length(vel); + vel[0] = mWeight * v1[0] + w * v0[0]; + vel[1] = mWeight * v1[1] + w * v0[1]; - if (velLength != 0.0f) { - float vel1Length = length(vel1); - float vel0Length = length(vel0); - float scale = (mWeight * vel1Length + weight1 * vel0Length) / velLength; + len = length(vel); - vel[0] = vel[0] * scale; - vel[1] = vel[1] * scale; + if (len != 0.0f) { + len = (mWeight * length(v1) + w * length(v0)) / len; + vel[0] = vel[0] * len; + vel[1] = vel[1] * len; } } else { - vel[1] = vel0[1]; - vel[0] = vel0[0]; + vel[1] = v0[1]; + vel[0] = v0[0]; } return true; From c63b6e56c4609b2fa94415d59e3c912ae63dba64 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 12:20:26 +0100 Subject: [PATCH 365/372] 90.089%: match RawEventChannel Eval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Indep/Src/EAGL4Anim/RawEventChannel.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp index cf2f971fb..996d733ed 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp @@ -51,19 +51,18 @@ void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tId i = currentIdx; - while (true) { - if (i >= numEvents) { - break; - } - if (events[i].triggerTime > currentTime) { - break; - } + if (i < numEvents) { + do { + if (events[i].triggerTime > currentTime) { + break; + } - eh = eventHandlers[events[i].eventId]; - if (eh) { - eh->HandleEvent(currentTime, events[i], extraData); - } - i++; + eh = eventHandlers[events[i].eventId]; + if (eh) { + eh->HandleEvent(currentTime, events[i], extraData); + } + i++; + } while (i < numEvents); } currentIdx = i; From 2daed21938d827342c4395d73edb02d2c1354049 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 12:29:17 +0100 Subject: [PATCH 366/372] 90.103%: tighten DynamicLoader GetSymbol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 7f7e35e77..fd68b6364 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -565,27 +565,28 @@ int DynamicLoader::GetCount() const { // TODO DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { DynamicLoader::Symbol r; - HashPointer *h = reinterpret_cast(handle); + HashPointer *h; ELF32_Sym *s; int iIndex; r.name = nullptr; r.data = nullptr; - if (!h) { + if (!handle) { return r; } + h = reinterpret_cast(handle); s = h->symtab; if (i < 0 || i >= h->symbols_num) { return r; } r.name = &h->strtab[s[i].st_name]; - r.type = r.name + strlen(r.name) + 1; + r.type = &h->strtab[s[i].st_name] + strlen(&h->strtab[s[i].st_name]) + 1; if (r.type[0] == 0x7F) { r.type++; } else { r.type--; } - r.isInternalRef = s[i].st_other > 5; + r.isInternalRef = s[i].st_other - 2 > 3; iIndex = s[i].st_shndx; if (s[i].st_other == 1) { r.data = reinterpret_cast(s[i].st_value); From 1621a9b4ae00ea6306452cbe404feee46f0fb9c6 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 12:30:40 +0100 Subject: [PATCH 367/372] 90.114%: match DynamicLoader GetSymbol Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index fd68b6364..3843682e7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -580,13 +580,13 @@ DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { return r; } r.name = &h->strtab[s[i].st_name]; - r.type = &h->strtab[s[i].st_name] + strlen(&h->strtab[s[i].st_name]) + 1; + r.type = &h->strtab[s[i].st_name] + strlen(r.name) + 1; if (r.type[0] == 0x7F) { r.type++; } else { r.type--; } - r.isInternalRef = s[i].st_other - 2 > 3; + r.isInternalRef = static_cast(s[i].st_other) - 2 > 3; iIndex = s[i].st_shndx; if (s[i].st_other == 1) { r.data = reinterpret_cast(s[i].st_value); From b00a0ec02231eda893da37aa5be3970e5e6eaf86 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 12:47:04 +0100 Subject: [PATCH 368/372] 90.127%: improve delta const bone idx Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp | 18 ++++++++---------- src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp | 18 ++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index ce845c3a9..b688c9f5a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -7,20 +7,18 @@ namespace EAGL4Anim { unsigned char *DeltaQ::GetConstBoneIdx() { - unsigned int numBones = mNumBones; - unsigned int binLength = 1u << mBinLengthPower; - unsigned int numBins = mNumKeys / binLength; - unsigned int remainder = mNumKeys - numBins * binLength; - int s = reinterpret_cast(this) + 0x12; + unsigned int binLen = GetBinLength(); + const int binSize = GetBinSize(); + unsigned char *s = GetBin(0); + int r = mNumKeys % binLen; - s += numBones * sizeof(DeltaQMinRange); - s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + s += binSize * (mNumKeys / binLen); - if (remainder != 0) { - s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); + if (r != 0) { + s += mNumBones * (((r - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); } - return reinterpret_cast(s); + return s; } DeltaQPhysical *DeltaQ::GetConstPhysical() { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index 78a3184ee..52c406e0e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -11,20 +11,18 @@ static void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath: namespace EAGL4Anim { unsigned char *DeltaQFast::GetConstBoneIdx() { - unsigned int numBones = mNumBones; - unsigned int binLength = 1u << mBinLengthPower; - unsigned int numBins = mNumKeys / binLength; - int remainder = mNumKeys - numBins * binLength; - int s = reinterpret_cast(this) + 0x12; + unsigned int binLen = GetBinLength(); + const int binSize = GetBinSize(); + unsigned char *s = GetBin(0); + int r = mNumKeys % binLen; - s += numBones * sizeof(DeltaQFastMinRange); - s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; + s += binSize * (mNumKeys / binLen); - if (remainder > 0) { - s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); + if (r > 0) { + s += mNumBones * (((r - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); } - return reinterpret_cast(s); + return s; } DeltaQFastPhysical *DeltaQFast::GetConstPhysical() { From 81b8cb667d039f7db8c9c3524a0f42e4f13a1743 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 13:14:01 +0100 Subject: [PATCH 369/372] 90.637% zTrack: match LoadPrecullerBooBooScript and polish world headers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Speed/Indep/Src/World/Clans.hpp | 2 +- src/Speed/Indep/Src/World/Scenery.cpp | 31 ++++++++++++--------- src/Speed/Indep/Src/World/Scenery.hpp | 4 +-- src/Speed/Indep/Src/World/Skids.hpp | 2 +- src/Speed/Indep/Src/World/TrackStreamer.cpp | 3 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/Speed/Indep/Src/World/Clans.hpp b/src/Speed/Indep/Src/World/Clans.hpp index 47e99c4f3..a41151bd4 100644 --- a/src/Speed/Indep/Src/World/Clans.hpp +++ b/src/Speed/Indep/Src/World/Clans.hpp @@ -9,7 +9,7 @@ #include "Speed/Indep/bWare/Inc/bMath.hpp" struct SkidSet; -struct eView; +class eView; void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *bbox2_min, const bVector3 *bbox2_max); diff --git a/src/Speed/Indep/Src/World/Scenery.cpp b/src/Speed/Indep/Src/World/Scenery.cpp index e6f7fe217..a86f9dcc8 100644 --- a/src/Speed/Indep/Src/World/Scenery.cpp +++ b/src/Speed/Indep/Src/World/Scenery.cpp @@ -58,7 +58,7 @@ class PrecullerBooBooManager { void Clr(bVector3 &pos) { int n = GetSectionNumber(pos); unsigned char *p = GetByte(n); - *p &= -GetBit(n) - 1U; + *p &= 0xFF - GetBit(n); } bool IsSet(bVector3 &pos) { @@ -316,17 +316,23 @@ void LoadPrecullerBooBooScript(const char *filename, bool reset) { SpeedScript script(filename, 1); while (script.GetNextCommand("BOOBOO:")) { - if (bStrICmp(script.GetNextArgumentString(), TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { - script.GetNextArgumentString(); - char *option = script.GetNextArgumentString(); - bool set_booboo = bStrICmp(option, "SET") == 0; - bool clr_booboo = bStrICmp(option, "CLR") == 0; - script.GetNextArgumentString(); - bVector3 pos = script.GetNextArgumentVector3(); - if (set_booboo) { - gPrecullerBooBooManager.Set(pos); - } else if (clr_booboo) { - gPrecullerBooBooManager.Clr(pos); + char *region = script.GetNextArgumentString(); + + if (bStrICmp(region, TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { + { + char *section = script.GetNextArgumentString(); + char *option = script.GetNextArgumentString(); + bool set_booboo = bStrICmp(option, "SET") == 0; + bool clr_booboo = bStrICmp(option, "CLR") == 0; + + (void)section; + script.GetNextArgumentString(); + bVector3 pos = script.GetNextArgumentVector3(); + if (set_booboo) { + gPrecullerBooBooManager.Set(pos); + } else if (clr_booboo) { + gPrecullerBooBooManager.Clr(pos); + } } } } @@ -1392,4 +1398,3 @@ void GrandSceneryCullInfo::StuffScenery(eView *view, int stuff_flags) { } } void ServicePreculler() {} - diff --git a/src/Speed/Indep/Src/World/Scenery.hpp b/src/Speed/Indep/Src/World/Scenery.hpp index d1faa1c97..8d299a561 100644 --- a/src/Speed/Indep/Src/World/Scenery.hpp +++ b/src/Speed/Indep/Src/World/Scenery.hpp @@ -167,9 +167,7 @@ struct SceneryTreeNode : public SceneryBoundingBox { short ChildCodes[5]; // offset 0x1A, size 0xA }; -// TODO -class ScenerySectionHeader : public bTNode { - public: +struct ScenerySectionHeader : public bTNode { void DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state); int IsVisible(SceneryCullInfo *scenery_cull_info); diff --git a/src/Speed/Indep/Src/World/Skids.hpp b/src/Speed/Indep/Src/World/Skids.hpp index afa3d3748..56ff784c7 100644 --- a/src/Speed/Indep/Src/World/Skids.hpp +++ b/src/Speed/Indep/Src/World/Skids.hpp @@ -12,7 +12,7 @@ #include "Speed/Indep/bWare/Inc/bSlotPool.hpp" #include "Speed/Indep/bWare/Inc/bWare.hpp" -struct eView; +class eView; extern SlotPool *SkidSetSlotPool; class SkidSegment { diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index ca047b45a..16e676191 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -585,7 +585,7 @@ int TrackStreamer::Loader(bChunk *chunk) { } else if (chunk_id == 0x34111) { pInfo = reinterpret_cast(chunk->GetData()); for (int i = 0; i < 2; i++) { - bEndianSwap32(i + pInfo->FileSize); + bEndianSwap32(&pInfo->FileSize[i]); } return 1; } else if (chunk_id == 0x34112) { @@ -2463,4 +2463,3 @@ void TrackStreamer::UnloadEverything() { FreeSectionMemory(); ClearCurrentZones(); } - From 5b06c10018706dc89dc31f20281fe0a5b4da1c81 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 13:23:44 +0100 Subject: [PATCH 370/372] zTrack/zEagl4Anim/tools: polish guidance and scope fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/code_style/SKILL.md | 2 ++ AGENTS.md | 4 ++++ src/Speed/Indep/Src/EAGL4Anim/FnCycle.h | 2 +- src/Speed/Indep/Src/World/TrackPath.cpp | 18 +++++++++--------- src/Speed/Indep/Src/World/TrackStreamer.cpp | 10 +++++----- tools/code_style.py | 7 ++++++- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 15f4779af..f5455ba9a 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -151,6 +151,7 @@ Foo::Foo() - Prefer readable blocks over stacked one-line statements when behavior does not depend on exact source shape. - In touched validation/parsing code, prefer explicit min/max or boundary checks over equivalent magic-constant arithmetic when the clearer form still compiles to the verified result. - In parser/state-table code, prefer named enums and enum-typed state variables over anonymous integer state codes when that rewrite is verified safe. +- In touched parser/script code, prefer keeping DWARF-listed consumed-token locals such as `region`, `section`, or `option` instead of chaining and discarding `GetNextArgument*` calls inline. When the original code used a small nested parse block, preserving that block shape can be necessary for exact DWARF even when objdiff already matches. ### Recovery markers @@ -206,3 +207,4 @@ Keep the cleanup only if the build succeeds and the relevant match status is unc - Reviewed fixups also remove stale bare recovery markers or replace them with context, and prefer existing list/node helpers over hand-written pointer/link rewiring. - Some reviewed fixups improved readability without losing match by replacing opaque range-check arithmetic with explicit bounds and by moving repeated pointer/boundary math behind short named helpers. - Other recurring review churn came from plain-`int` address helpers, stray local `.cpp` prototypes for shared functions, and integer-coded parser states where named enums were clearer but still matched. +- Recent `zTrack` polish also showed that parser-like code should keep DWARF-listed consumed-token locals and the original nested block shape instead of compressing everything into inline/discarded `GetNextArgument*` calls. diff --git a/AGENTS.md b/AGENTS.md index 771ecd9a7..2ce218626 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -475,6 +475,10 @@ register assignments but does NOT affect integer register assignments (and vice loop form that keeps the temporary inside the same block as the original DWARF. In practice, changing a `for (...; ...; x = next)` into a `while (...) { T *next = ...; ...; x = next; }` can fix DWARF-only scope mismatches without changing codegen. +- In parser/script code, do not collapse DWARF-listed consumed tokens into chained discarded + calls. If the original debug info shows locals such as `region`, `section`, or `option`, + keep those named locals and the small nested parse block; that can preserve exact DWARF + while leaving objdiff unchanged. ### Slot-pooled delete paths diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h index ad32e6b8e..8ecfde059 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h @@ -1,11 +1,11 @@ #ifndef EAGL4ANIM_FNCYCLE_H #define EAGL4ANIM_FNCYCLE_H -#include "Speed/Indep/Src/EAGL4Anim/eagl4supportdef.h" #ifdef EA_PRAGMA_ONCE_SUPPORTED #pragma once #endif +#include "Speed/Indep/Src/EAGL4Anim/eagl4supportdef.h" #include "AnimUtil.h" #include "FnAnim.h" diff --git a/src/Speed/Indep/Src/World/TrackPath.cpp b/src/Speed/Indep/Src/World/TrackPath.cpp index a4a348e97..089c08374 100644 --- a/src/Speed/Indep/Src/World/TrackPath.cpp +++ b/src/Speed/Indep/Src/World/TrackPath.cpp @@ -172,9 +172,8 @@ TrackPathZone *TrackPathManager::FindZone(const bVector2 *position, eTrackPathZo } else { const float cached_radius = 64.0f; TrackPathZone *first_zone; - TrackPathZone *last_zone; + TrackPathZone *last_zone = zone_info->pLastZone; - last_zone = zone_info->pLastZone; first_zone = zone_info->pFirstZone; zone_info->CachedBBoxMin.x = position->x - cached_radius; @@ -200,8 +199,8 @@ TrackPathZone *TrackPathManager::FindZone(const bVector2 *position, eTrackPathZo TrackPathZone *found_zone = 0; if (!cache_valid) { - TrackPathZone *last_zone = zone_info->pLastZone; TrackPathZone *first_zone; + TrackPathZone *last_zone = zone_info->pLastZone; first_zone = zone_info->pFirstZone; zone_info->NumCacheRebuilds += 1; @@ -209,12 +208,14 @@ TrackPathZone *TrackPathManager::FindZone(const bVector2 *position, eTrackPathZo first_zone = prev_zone->GetMemoryImageNext(); } - TrackPathZone *zone = first_zone; - while (zone < last_zone && position && - (!bBoundingBoxIsInside(&zone->BBoxMin, &zone->BBoxMax, position, 0.0f) || !zone->IsPointInside(position))) { - zone = zone->GetMemoryImageNext(); + { + TrackPathZone *zone = first_zone; + while (zone < last_zone && position && + (!bBoundingBoxIsInside(&zone->BBoxMin, &zone->BBoxMax, position, 0.0f) || !zone->IsPointInside(position))) { + zone = zone->GetMemoryImageNext(); + } + found_zone = zone; } - found_zone = zone; } else { int first_zone_index = 0; zone_info->NumCacheHits += 1; @@ -286,4 +287,3 @@ float TrackPathZone::GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a bCopy(segment_point_b, &Points[Closest1]); return d0; } - diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index 16e676191..65f5f5e0c 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -634,7 +634,6 @@ int TrackStreamer::Unloader(bChunk *chunk) { void TrackStreamer::ClearCurrentZones() { for (int position_number = 0; position_number < 2; position_number++) { StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; - position_entry->AmountLoaded = 0; position_entry->CurrentZone = 0; position_entry->BeginLoadingTime = 0.0f; position_entry->BeginLoadingPosition.x = 0.0f; @@ -642,17 +641,18 @@ void TrackStreamer::ClearCurrentZones() { position_entry->NumSectionsToLoad = 0; position_entry->NumSectionsLoaded = 0; position_entry->AmountToLoad = 0; + position_entry->AmountLoaded = 0; } CurrentZoneFarLoad = true; StartLoadingTime = 0.0f; + NumJettisonedSections = 0; + LoadingPhase = LOADING_IDLE; + LoadingBacklog = 0.0f; CurrentZoneOutOfMemory = false; CurrentZoneAllocatedButIncomplete = false; CurrentZoneNonReplayLoad = false; - LoadingPhase = LOADING_IDLE; - LoadingBacklog = 0.0f; CurrentZoneName[0] = 0; - NumJettisonedSections = 0; MemorySafetyMargin = 0; AmountJettisoned = 0; bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); @@ -2119,10 +2119,10 @@ void TrackStreamer::SwitchZones(short *current_zones) { FreeSectionMemory(); SetLoadingPhase(ALLOCATING_TEXTURE_SECTIONS); NumJettisonedSections = 0; + AmountJettisoned = 0; CurrentZoneOutOfMemory = false; CurrentZoneAllocatedButIncomplete = false; MemorySafetyMargin = 0; - AmountJettisoned = 0; bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); AssignLoadingPriority(); CalculateLoadingBacklog(); diff --git a/tools/code_style.py b/tools/code_style.py index 61c3f2ee3..3a1129963 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -416,6 +416,7 @@ def audit_forward_declarations( path: str, text: str, touched_lines: Optional[Set[int]] ) -> List[Finding]: findings: List[Finding] = [] + current_rel = relpath(path) for idx, line in enumerate(text.splitlines(), 1): if touched_lines is not None and idx not in touched_lines: continue @@ -424,7 +425,11 @@ def audit_forward_declarations( continue name = match.group(2) - headers = header_declaration_paths(name, path, idx) + headers = [ + header + for header in header_declaration_paths(name, path, idx) + if header != current_rel + ] if not headers: continue From cd8b33080ee20b6879775a7f7f099e90e4917037 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 13:32:58 +0100 Subject: [PATCH 371/372] 90.644% zTrack: improve ClearCurrentZones ordering --- src/Speed/Indep/Src/World/TrackStreamer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index 65f5f5e0c..250f3080a 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -648,7 +648,7 @@ void TrackStreamer::ClearCurrentZones() { StartLoadingTime = 0.0f; NumJettisonedSections = 0; LoadingPhase = LOADING_IDLE; - LoadingBacklog = 0.0f; + LoadingBacklog = StartLoadingTime; CurrentZoneOutOfMemory = false; CurrentZoneAllocatedButIncomplete = false; CurrentZoneNonReplayLoad = false; From 4fb10e6fb1f510fb917455e0d68052df0aea7368 Mon Sep 17 00:00:00 2001 From: Johann Berger Date: Thu, 26 Mar 2026 13:45:30 +0100 Subject: [PATCH 372/372] 90.644% zTrack: restore UnloadSection helper ownership --- src/Speed/Indep/Src/World/TrackStreamer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index 250f3080a..b310dbdf4 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -937,16 +937,17 @@ bool TrackStreamer::WillUnloadBlock(TrackStreamingSection *section) { } void TrackStreamer::UnloadSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); if (section->Status == TrackStreamingSection::ACTIVATED) { UnactivateSection(section); } if (section->Status == TrackStreamingSection::LOADED) { if (WillUnloadBlock(section)) { - WaitForFrameBufferSwapDisabled = 1; + DisableWaitForFrameBufferSwap(); eWaitUntilRenderingDone(); - WaitForFrameBufferSwapDisabled = 0; - LastWaitUntilRenderingDoneFrameCount = eFrameCounter; + EnableWaitForFrameBufferSwap(); + LastWaitUntilRenderingDoneFrameCount = eGetFrameCounter(); } section->UnactivatedFrameCount = 0;