fix(physics): port retail slope landing stop
This commit is contained in:
parent
1d8371dbe5
commit
5a0f9868a6
13 changed files with 870 additions and 103 deletions
125
tools/analyze_269_slope_stop_capture.py
Normal file
125
tools/analyze_269_slope_stop_capture.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Summarize an ACDREAM_CAPTURE_PLAYER_QUANTA JSONL for issue #269."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def vector(value: dict[str, Any]) -> tuple[float, float, float]:
|
||||
return float(value["x"]), float(value["y"]), float(value["z"])
|
||||
|
||||
|
||||
def length(value: tuple[float, float, float]) -> float:
|
||||
return math.sqrt(sum(component * component for component in value))
|
||||
|
||||
|
||||
def horizontal(value: tuple[float, float, float]) -> float:
|
||||
return math.hypot(value[0], value[1])
|
||||
|
||||
|
||||
def subtract(
|
||||
left: tuple[float, float, float],
|
||||
right: tuple[float, float, float],
|
||||
) -> tuple[float, float, float]:
|
||||
return tuple(a - b for a, b in zip(left, right, strict=True))
|
||||
|
||||
|
||||
def scale(
|
||||
value: tuple[float, float, float],
|
||||
scalar: float,
|
||||
) -> tuple[float, float, float]:
|
||||
return tuple(component * scalar for component in value)
|
||||
|
||||
|
||||
def input_active(record: dict[str, Any]) -> bool:
|
||||
state = record["input"]
|
||||
return any(
|
||||
bool(state[key])
|
||||
for key in (
|
||||
"forward",
|
||||
"backward",
|
||||
"strafeLeft",
|
||||
"strafeRight",
|
||||
"turnLeft",
|
||||
"turnRight",
|
||||
"jump",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("capture", type=Path)
|
||||
parser.add_argument(
|
||||
"--tail",
|
||||
type=int,
|
||||
default=45,
|
||||
help="quanta printed after the final directional-input release",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
with args.capture.open("r", encoding="utf-8") as stream:
|
||||
records = [json.loads(line) for line in stream if line.strip()]
|
||||
|
||||
if not records:
|
||||
print("capture contains no physics quanta")
|
||||
return 2
|
||||
|
||||
release_indices = [
|
||||
index
|
||||
for index in range(1, len(records))
|
||||
if input_active(records[index - 1]) and not input_active(records[index])
|
||||
]
|
||||
start = release_indices[-1] if release_indices else max(0, len(records) - args.tail)
|
||||
stop = min(len(records), start + args.tail)
|
||||
|
||||
print(
|
||||
"seq dt walk(pre/post) |v|pre |v|fric |v|commit "
|
||||
"horizCommit rootDelta collision fsf"
|
||||
)
|
||||
for record in records[start:stop]:
|
||||
dt = float(record["dt"])
|
||||
pre = record["preIntegration"]
|
||||
post_integration = record["postIntegration"]
|
||||
post_commit = record["postCommit"]
|
||||
pre_velocity = vector(pre["velocity"])
|
||||
integrated_velocity = vector(post_integration["velocity"])
|
||||
acceleration = vector(pre["acceleration"])
|
||||
friction_velocity = subtract(
|
||||
integrated_velocity,
|
||||
scale(acceleration, dt),
|
||||
)
|
||||
commit_velocity = vector(post_commit["velocity"])
|
||||
transient_pre = int(pre["transientState"])
|
||||
transient_post = int(post_commit["transientState"])
|
||||
pre_walk = (transient_pre & 0x2) != 0
|
||||
post_walk = (transient_post & 0x2) != 0
|
||||
root_delta = length(vector(record["rootAndManagerDelta"]))
|
||||
resolve = record["resolve"]
|
||||
collision = "yes" if resolve["collisionNormalValid"] else "no"
|
||||
print(
|
||||
f'{record["sequence"]:5d} {dt:0.5f} '
|
||||
f"{int(pre_walk)}/{int(post_walk)} "
|
||||
f"{length(pre_velocity):8.4f} "
|
||||
f"{length(friction_velocity):8.4f} "
|
||||
f"{length(commit_velocity):8.4f} "
|
||||
f"{horizontal(commit_velocity):8.4f} "
|
||||
f"{root_delta:8.4f} {collision:>3s} "
|
||||
f'{post_commit["framesStationaryFall"]:d}'
|
||||
)
|
||||
|
||||
if release_indices:
|
||||
print(f"\nlast directional-input release: sequence {records[start]['sequence']}")
|
||||
else:
|
||||
print("\nno directional-input release edge found; showing capture tail")
|
||||
print(f"records: {len(records)}, displayed: {stop - start}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
48
tools/cdb/issue269-slope-stop.cdb
Normal file
48
tools/cdb/issue269-slope-stop.cdb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
$$
|
||||
$$ Issue #269 retail slope-stop trace.
|
||||
$$
|
||||
$$ This script must only be attached to the Sept 2013 EoR acclient.exe
|
||||
$$ paired with refs/acclient.pdb (GUID 9e847e2f-777c-4bd9-886c-22256bb87f32).
|
||||
$$ The PowerShell runner verifies that pairing before attach.
|
||||
$$
|
||||
$$ It records the player object's complete physics-integrator boundary and
|
||||
$$ handle_all_collisions boundary as raw IEEE-754 bits. That is the minimum
|
||||
$$ runtime evidence needed to distinguish friction cadence, contact loss, and
|
||||
$$ post-sweep collision response without guessing from visible distance.
|
||||
$$
|
||||
|
||||
.logopen ${ARG_LOG_PATH}
|
||||
.sympath ${ARG_SYMBOL_PATH}
|
||||
.symopt+ 0x40
|
||||
.reload /f acclient.exe
|
||||
|
||||
r $t0 = 0
|
||||
r $t1 = 0
|
||||
|
||||
$$ CPhysicsObj offsets from PDB dt:
|
||||
$$ state +0xa8, transient +0xac, friction +0xbc,
|
||||
$$ velocity +0xe0, acceleration +0xec, contact_plane.N +0x130.
|
||||
$$ UpdatePhysicsInternal entry: ecx=this, [esp+4]=dt, [esp+8]=Frame*.
|
||||
bp acclient!CPhysicsObj::UpdatePhysicsInternal ".if (@ecx == poi(acclient!CPhysicsObj::player_object)) { r $t0=@$t0+1; .printf \"[UPI-IN] q=%d dt_h=%08X state=%08X transient=%08X friction_h=%08X vx_h=%08X vy_h=%08X vz_h=%08X ax_h=%08X ay_h=%08X az_h=%08X nx_h=%08X ny_h=%08X nz_h=%08X fx_h=%08X fy_h=%08X fz_h=%08X\\n\", @$t0, dwo(@esp+4), dwo(@ecx+0xa8), dwo(@ecx+0xac), dwo(@ecx+0xbc), dwo(@ecx+0xe0), dwo(@ecx+0xe4), dwo(@ecx+0xe8), dwo(@ecx+0xec), dwo(@ecx+0xf0), dwo(@ecx+0xf4), dwo(@ecx+0x130), dwo(@ecx+0x134), dwo(@ecx+0x138), dwo(poi(@esp+8)+0x34), dwo(poi(@esp+8)+0x38), dwo(poi(@esp+8)+0x3c) }; gc"
|
||||
|
||||
$$ UpdatePhysicsInternal epilogue: edi=this, ebx=Frame*, velocity has
|
||||
$$ completed friction + acceleration and Frame contains the integrated delta.
|
||||
$$ The terminal hit intentionally omits gc so the top-level qd detaches cleanly.
|
||||
bp acclient+0x0011093a ".if (@edi == poi(acclient!CPhysicsObj::player_object)) { .printf \"[UPI-OUT] q=%d transient=%08X vx_h=%08X vy_h=%08X vz_h=%08X fx_h=%08X fy_h=%08X fz_h=%08X\\n\", @$t0, dwo(@edi+0xac), dwo(@edi+0xe0), dwo(@edi+0xe4), dwo(@edi+0xe8), dwo(@ebx+0x34), dwo(@ebx+0x38), dwo(@ebx+0x3c); .if (@$t0 < ${ARG_MAX_QUANTA}) { gc } } .else { gc }"
|
||||
|
||||
$$ handle_all_collisions entry: ecx=this, [esp+4]=COLLISIONINFO*.
|
||||
$$ COLLISIONINFO offsets from PDB dt: normal-valid +0x48,
|
||||
$$ normal +0x4c, frames_stationary_fall +0x80.
|
||||
bp acclient!CPhysicsObj::handle_all_collisions ".if (@ecx == poi(acclient!CPhysicsObj::player_object)) { r $t1=@$t1+1; .printf \"[HAC-IN] h=%d q=%d fsf=%d normalValid=%d nx_h=%08X ny_h=%08X nz_h=%08X vx_h=%08X vy_h=%08X vz_h=%08X transient=%08X\\n\", @$t1, @$t0, dwo(poi(@esp+4)+0x80), dwo(poi(@esp+4)+0x48), dwo(poi(@esp+4)+0x4c), dwo(poi(@esp+4)+0x50), dwo(poi(@esp+4)+0x54), dwo(@ecx+0xe0), dwo(@ecx+0xe4), dwo(@ecx+0xe8), dwo(@ecx+0xac) }; gc"
|
||||
|
||||
$$ Four epilogues correspond to fsf 0, 1, 2, and 3. At each address esi
|
||||
$$ still owns this and the final velocity/transient state has been written.
|
||||
bp acclient+0x00114977 ".if (@esi == poi(acclient!CPhysicsObj::player_object)) { .printf \"[HAC-OUT] h=%d q=%d fsfClass=0 vx_h=%08X vy_h=%08X vz_h=%08X transient=%08X\\n\", @$t1, @$t0, dwo(@esi+0xe0), dwo(@esi+0xe4), dwo(@esi+0xe8), dwo(@esi+0xac) }; gc"
|
||||
bp acclient+0x00114997 ".if (@esi == poi(acclient!CPhysicsObj::player_object)) { .printf \"[HAC-OUT] h=%d q=%d fsfClass=1 vx_h=%08X vy_h=%08X vz_h=%08X transient=%08X\\n\", @$t1, @$t0, dwo(@esi+0xe0), dwo(@esi+0xe4), dwo(@esi+0xe8), dwo(@esi+0xac) }; gc"
|
||||
bp acclient+0x001149b1 ".if (@esi == poi(acclient!CPhysicsObj::player_object)) { .printf \"[HAC-OUT] h=%d q=%d fsfClass=2 vx_h=%08X vy_h=%08X vz_h=%08X transient=%08X\\n\", @$t1, @$t0, dwo(@esi+0xe0), dwo(@esi+0xe4), dwo(@esi+0xe8), dwo(@esi+0xac) }; gc"
|
||||
bp acclient+0x001149c6 ".if (@esi == poi(acclient!CPhysicsObj::player_object)) { .printf \"[HAC-OUT] h=%d q=%d fsfClass=3 vx_h=%08X vy_h=%08X vz_h=%08X transient=%08X\\n\", @$t1, @$t0, dwo(@esi+0xe0), dwo(@esi+0xe4), dwo(@esi+0xe8), dwo(@esi+0xac) }; gc"
|
||||
|
||||
.printf "issue269 slope-stop probe armed; maxQuanta=${ARG_MAX_QUANTA}\\n"
|
||||
g
|
||||
.echo === DETACHING AFTER BOUNDED CAPTURE ===
|
||||
qd
|
||||
74
tools/cdb/run-issue269-slope-stop.ps1
Normal file
74
tools/cdb/run-issue269-slope-stop.ps1
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidatePattern('^[A-Za-z0-9_.-]+$')]
|
||||
[string]$ScenarioTag = "slope-stop",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateRange(60, 1800)]
|
||||
[int]$MaxQuanta = 450
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$cdbExe = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe"
|
||||
if (-not (Test-Path -LiteralPath $cdbExe)) {
|
||||
throw "cdb.exe was not found at '$cdbExe'."
|
||||
}
|
||||
|
||||
$retail = Get-CimInstance Win32_Process -Filter "Name = 'acclient.exe'" |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $retail) {
|
||||
throw "No live retail acclient.exe process was found."
|
||||
}
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
||||
$checkScript = Join-Path $repoRoot "tools\pdb-extract\check_exe_pdb.py"
|
||||
$pairing = & py $checkScript $retail.ExecutablePath 2>&1 | Out-String
|
||||
if ($pairing -notmatch "=== MATCH:") {
|
||||
throw @"
|
||||
The live retail executable does not pair with the named-retail PDB.
|
||||
Process path: $($retail.ExecutablePath)
|
||||
$pairing
|
||||
"@
|
||||
}
|
||||
|
||||
$symbolCandidates = @(
|
||||
(Join-Path $repoRoot "refs"),
|
||||
(Join-Path $env:USERPROFILE "source\repos\acdream\refs"),
|
||||
(Join-Path $env:USERPROFILE ".windbg\x86\sym")
|
||||
)
|
||||
$symbolPath = $symbolCandidates |
|
||||
Where-Object { Test-Path -LiteralPath $_ } |
|
||||
Select-Object -First 1
|
||||
if ([string]::IsNullOrWhiteSpace($symbolPath)) {
|
||||
throw "The matching acclient.pdb symbol directory could not be found."
|
||||
}
|
||||
|
||||
$templatePath = Join-Path $PSScriptRoot "issue269-slope-stop.cdb"
|
||||
$artifactDir = Join-Path $repoRoot "artifacts\issue269"
|
||||
New-Item -ItemType Directory -Path $artifactDir -Force | Out-Null
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$logPath = Join-Path $artifactDir "$ScenarioTag-retail-$timestamp.log"
|
||||
$scriptPath = Join-Path $env:TEMP "issue269-$ScenarioTag-$timestamp.cdb"
|
||||
|
||||
$script = Get-Content -LiteralPath $templatePath -Raw
|
||||
$script = $script.Replace('${ARG_LOG_PATH}', $logPath)
|
||||
$script = $script.Replace('${ARG_SYMBOL_PATH}', $symbolPath)
|
||||
$script = $script.Replace('${ARG_MAX_QUANTA}', $MaxQuanta.ToString(
|
||||
[System.Globalization.CultureInfo]::InvariantCulture))
|
||||
Set-Content -LiteralPath $scriptPath -Value $script -Encoding ASCII
|
||||
|
||||
Write-Host "Attaching cdb to retail PID $($retail.ProcessId)."
|
||||
Write-Host "Capture: $logPath"
|
||||
Write-Host "Perform the slope run, release movement, and let the character settle."
|
||||
Write-Host "The probe detaches after $MaxQuanta player physics quanta."
|
||||
|
||||
try {
|
||||
& $cdbExe -p $retail.ProcessId -cf $scriptPath 2>&1 |
|
||||
Out-File -LiteralPath "$logPath.console" -Encoding ASCII
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $scriptPath -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Retail capture complete: $logPath"
|
||||
Loading…
Add table
Add a link
Reference in a new issue