One-line per-iteration emission helper for the CheckOtherCells multi-cell BSP loop. Captures primary/other cell ids, BSP result, and halted flag for direct comparison to retail's CTransition::check_other_cells loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
462 lines
21 KiB
C#
462 lines
21 KiB
C#
using System;
|
|
using System.Numerics;
|
|
|
|
namespace AcDream.Core.Physics;
|
|
|
|
/// <summary>
|
|
/// L.2a slice 1 (2026-05-12) — runtime-toggleable physics probe flags.
|
|
/// Initialized from env vars at process start; flippable at runtime via
|
|
/// the DebugPanel mirror (or by direct assignment). Log call sites read
|
|
/// these statics so a checkbox toggle takes effect on the next resolve
|
|
/// without relaunching.
|
|
///
|
|
/// <para>
|
|
/// L.2d slice 1 (2026-05-13) adds <see cref="ProbeBuildingEnabled"/> +
|
|
/// the <see cref="LastBspHitPoly"/> diagnostic side-channel. Future
|
|
/// slices may fold the older <c>ACDREAM_DUMP_*</c> env vars into this
|
|
/// class for unified runtime toggling. Until then, those older flags
|
|
/// remain sticky-at-startup per their original implementation.
|
|
/// </para>
|
|
/// </summary>
|
|
public static class PhysicsDiagnostics
|
|
{
|
|
/// <summary>
|
|
/// When true, <see cref="PhysicsEngine.ResolveWithTransition"/> emits
|
|
/// one structured <c>[resolve]</c> line per call: input + target +
|
|
/// output position/cell, grounded state, contact-plane status,
|
|
/// collision-normal validity, walkable polygon status, moving entity
|
|
/// id. Initial state from <c>ACDREAM_PROBE_RESOLVE=1</c>.
|
|
/// </summary>
|
|
public static bool ProbeResolveEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_RESOLVE") == "1";
|
|
|
|
/// <summary>
|
|
/// When true, every change to <c>PlayerMovementController.CellId</c>
|
|
/// emits one <c>[cell-transit]</c> line: old → new cell, current
|
|
/// world position, reason tag (<c>resolver</c> / <c>teleport</c>).
|
|
/// Initial state from <c>ACDREAM_PROBE_CELL=1</c>.
|
|
/// </summary>
|
|
public static bool ProbeCellEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL") == "1";
|
|
|
|
/// <summary>
|
|
/// L.2d slice 1 (2026-05-13). When true, every BSP-shadow-entry hit
|
|
/// attributed by <c>TransitionTypes.FindObjCollisions</c> emits a
|
|
/// multi-line <c>[resolve-bldg]</c> entry: which part (partIdx vs 0),
|
|
/// physics-BSP root radius vs visual AABB radius, world-space entity
|
|
/// origin, and the specific hit polygon's vertices in both
|
|
/// object-local and world space. Designed to distinguish the three
|
|
/// L.2d hypotheses (wrong BSP loaded / over-registered parts /
|
|
/// BSPQuery flaw) from a single Holtburg-doorway capture.
|
|
///
|
|
/// <para>
|
|
/// Also gates a one-time <c>[entity-source]</c> log line at every
|
|
/// <c>ShadowObjects.Register(...)</c> call site in <c>GameWindow</c>
|
|
/// — makes <c>entityId=0xA9B479</c> in a probe line greppable to its
|
|
/// source registration within the same log file.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Initial state from <c>ACDREAM_PROBE_BUILDING=1</c>. Mirrorable
|
|
/// via <c>DebugVM.ProbeBuilding</c> when <c>ACDREAM_DEVTOOLS=1</c>.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Spec: <c>docs/superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md</c>.
|
|
/// </para>
|
|
/// </summary>
|
|
public static bool ProbeBuildingEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_BUILDING") == "1";
|
|
|
|
/// <summary>
|
|
/// L.2d slice 1 (2026-05-13). Diagnostic side-channel: the
|
|
/// <see cref="ResolvedPolygon"/> that <see cref="BSPQuery"/>
|
|
/// recorded for the most recent collision-normal write.
|
|
/// <see cref="TransitionTypes.FindObjCollisions"/> clears this to
|
|
/// <see langword="null"/> before each shadow-entry test and reads it
|
|
/// back after, so emitting the <c>[resolve-bldg]</c> probe line can
|
|
/// reference the actual hit poly without plumbing an out-param
|
|
/// through BSPQuery's recursive private methods.
|
|
///
|
|
/// <para>
|
|
/// Written by <see cref="BSPQuery"/> only when
|
|
/// <see cref="ProbeBuildingEnabled"/> is true, so this stays
|
|
/// zero-cost in normal play. Cylinder collisions leave this
|
|
/// <see langword="null"/> — the probe line emits
|
|
/// <c>hitPoly: n/a (cylinder)</c> in that case.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Not threadsafe — physics runs on a single thread. If that
|
|
/// changes, this needs <c>[ThreadStatic]</c> or rethink. Deviation
|
|
/// from spec component 4 (which described an out-param); the
|
|
/// side-channel keeps BSPQuery's signature stable and the diagnostic
|
|
/// path off the production code surface.
|
|
/// </para>
|
|
/// </summary>
|
|
public static ResolvedPolygon? LastBspHitPoly { get; set; }
|
|
|
|
/// <summary>
|
|
/// B.6 slice 1 (2026-05-14) — baseline trace for the local-player
|
|
/// server-initiated auto-walk path (issue #63). When true, the
|
|
/// following events emit one-line <c>[autowalk-*]</c> logs:
|
|
/// <list type="bullet">
|
|
/// <item><description><c>[autowalk-out]</c> on every <c>SendUse</c>
|
|
/// / <c>SendPickUp</c> the local player issues — these are the
|
|
/// packets that may trigger ACE's server-side <c>CreateMoveToChain</c>
|
|
/// when the target is out of <c>WithinUseRadius</c>.</description></item>
|
|
/// <item><description><c>[autowalk-mt]</c> on every inbound
|
|
/// <c>UpdateMotion</c> for the local player — captures the
|
|
/// <c>MovementType + MoveToPath + speed/runRate</c> ACE sends.</description></item>
|
|
/// <item><description><c>[autowalk-up]</c> on every inbound
|
|
/// <c>UpdatePosition</c> for the local player — answers "what's
|
|
/// ACE's broadcast cadence during auto-walk?"</description></item>
|
|
/// </list>
|
|
/// Initial state from <c>ACDREAM_PROBE_AUTOWALK=1</c>.
|
|
///
|
|
/// <para>
|
|
/// Spec: <c>docs/superpowers/specs/2026-05-14-phase-b6-design.md</c>
|
|
/// §"Required investigation".
|
|
/// </para>
|
|
/// </summary>
|
|
public static bool ProbeAutoWalkEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_AUTOWALK") == "1";
|
|
|
|
/// <summary>
|
|
/// 2026-05-16. Logs one line per `IsUseableTarget` call that takes
|
|
/// the null-useability fallback path (creature pass / BF_DOOR pass /
|
|
/// BF_LIFESTONE pass / etc.). Used to measure how often ACE's seed
|
|
/// DB ships entities without `_useability` set — settles whether
|
|
/// the fallback is live code or theoretical defense.
|
|
///
|
|
/// <para>
|
|
/// Retail has NO fallback; null/zero useability blocks Use entirely
|
|
/// (acclient_2013_pseudo_c.txt:402923 ItemHolder::UseObject —
|
|
/// IsUseable==0 falls through to "cannot be used" branch). Our
|
|
/// fallback exists because ACE genuinely sends null for many seed
|
|
/// weenies. The probe quantifies "many".
|
|
/// </para>
|
|
///
|
|
/// <para>Toggle via env var <c>ACDREAM_PROBE_USEABILITY_FALLBACK=1</c>
|
|
/// or DebugPanel checkbox.</para>
|
|
/// </summary>
|
|
public static bool ProbeUseabilityFallbackEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_USEABILITY_FALLBACK") == "1";
|
|
|
|
/// <summary>
|
|
/// L.4-diag (2026-04-30) → promoted into <see cref="PhysicsDiagnostics"/>
|
|
/// 2026-05-16 per CLAUDE.md "Code Structure Rules" §5 (diagnostic owner
|
|
/// classes, not per-call-site env reads). Gates the <c>[steep-roof]</c>
|
|
/// trace family that fires from four sites during the rooftop-bounce
|
|
/// investigation:
|
|
/// <list type="bullet">
|
|
/// <item><description><c>PhysicsEngine.ResolveWithTransition</c> —
|
|
/// <c>[steep-roof] KILL-VELOCITY-APPLIED</c> when retail-faithful
|
|
/// <c>kill_velocity</c> zeroes the body's velocity on steep-slope
|
|
/// impact.</description></item>
|
|
/// <item><description><c>TransitionTypes</c> (<c>FindEnvCollisions</c>
|
|
/// post-step) — per-frame plane-normal trace on the active
|
|
/// <see cref="CollisionInfo"/>.</description></item>
|
|
/// <item><description><c>PlayerMovementController</c> — two sites
|
|
/// emitting <c>[steep-roof]</c> + the per-frame bounce trace when
|
|
/// the post-collision velocity disagrees with retail.</description></item>
|
|
/// </list>
|
|
/// Initial state from <c>ACDREAM_DUMP_STEEP_ROOF=1</c>. Runtime-toggleable
|
|
/// via the property setter; not yet wired to a DebugPanel checkbox (open
|
|
/// follow-up if a debugging session calls for it).
|
|
/// </summary>
|
|
public static bool DumpSteepRoofEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_DUMP_STEEP_ROOF") == "1";
|
|
|
|
/// <summary>
|
|
/// Indoor walking Phase 1 (2026-05-19). When true, emits one
|
|
/// <c>[indoor-bsp]</c> line per <see cref="BSPQuery.FindCollisions"/>
|
|
/// call made from <see cref="Transition.FindEnvCollisions"/>'s indoor
|
|
/// cell-BSP branch. Captures the cell id, sphere local position,
|
|
/// resulting <see cref="TransitionState"/>, and the hit poly's id,
|
|
/// local-normal, and side-type — pinpoints why indoor collision
|
|
/// returns spurious collisions (#84) and helps cross-check the
|
|
/// outdoor-in approach path (#85).
|
|
///
|
|
/// <para>
|
|
/// While true, this also un-gates the diagnostic
|
|
/// <see cref="LastBspHitPoly"/> side-channel inside
|
|
/// <see cref="BSPQuery"/> — see the OR'd condition at every poly
|
|
/// write site. Zero-cost when off.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Initial state from <c>ACDREAM_PROBE_INDOOR_BSP=1</c>.
|
|
/// Runtime-toggleable via DebugPanel.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Spec: <c>docs/superpowers/specs/2026-05-19-indoor-walking-phase1-bsp-cluster-design.md</c>.
|
|
/// </para>
|
|
/// </summary>
|
|
public static bool ProbeIndoorBspEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_INDOOR_BSP") == "1";
|
|
|
|
/// <summary>
|
|
/// Indoor walking Phase D follow-up (2026-05-19). When true, emits one
|
|
/// <c>[cell-cache]</c> line each time <see cref="PhysicsDataCache.CacheCellStruct"/>
|
|
/// caches a new EnvCell. Reports per-cell polygon counts and BSP root
|
|
/// structure so the caller can cross-reference with <c>[indoor-bsp]</c>
|
|
/// lines to distinguish between:
|
|
/// <list type="bullet">
|
|
/// <item><description>Empty data (physicsPolyCount=0 or resolvedCount=0)
|
|
/// — candidate (a)/(c) in the poly=n/a investigation.</description></item>
|
|
/// <item><description>Non-zero polygon counts but bspRootPolyCount=0 at
|
|
/// root + tree has children — correct structure for non-leaf root,
|
|
/// leaves hold the poly refs; not a bug.</description></item>
|
|
/// <item><description>Non-zero polygon counts but bspRootPolyCount=0 at
|
|
/// root AND root is a leaf (bspRootHasChildren=false) — BSP leaf with
|
|
/// zero poly refs, candidate (b)/(d).</description></item>
|
|
/// </list>
|
|
/// This diagnostic fires at most once per EnvCell (cache is no-op after
|
|
/// first population). It does NOT have a DebugPanel mirror yet — this is
|
|
/// a one-shot capture tool, not a persistent toggle. Promote to full
|
|
/// infrastructure after the root cause is identified.
|
|
///
|
|
/// <para>Initial state from <c>ACDREAM_PROBE_CELL_CACHE=1</c>.</para>
|
|
/// </summary>
|
|
public static bool ProbeCellCacheEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL_CACHE") == "1";
|
|
|
|
/// <summary>
|
|
/// ContactPlane retention spike (2026-05-20). When true, every write to
|
|
/// <c>CollisionInfo.ContactPlane{,Valid,CellId,IsWater}</c> and
|
|
/// <c>LastKnownContactPlane{,Valid,CellId,IsWater}</c> emits one
|
|
/// <c>[cp-write]</c> line: field, old → new value, caller method (walked
|
|
/// from the stack), and source line. Maps the per-frame lifecycle of the
|
|
/// contact plane to confirm/refute the hypothesis that
|
|
/// <c>FindEnvCollisions</c> indoor branch is rewriting CP every frame
|
|
/// instead of retaining it across frames.
|
|
///
|
|
/// <para>
|
|
/// Only logs when the value actually changes (suppresses no-op writes to
|
|
/// reduce log volume). Initial state from
|
|
/// <c>ACDREAM_PROBE_CONTACT_PLANE=1</c>. Spike-only — remove once the fix
|
|
/// lands and the diagnostic value is captured.
|
|
/// </para>
|
|
/// </summary>
|
|
public static bool ProbeContactPlaneEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CONTACT_PLANE") == "1";
|
|
|
|
/// <summary>
|
|
/// Indoor walking ISSUES #83 H-disambiguation spike (2026-05-21).
|
|
/// When true, two diagnostic emissions activate:
|
|
/// <list type="bullet">
|
|
/// <item><description>One <c>[walk-miss]</c> line per
|
|
/// <see cref="Transition.TryFindIndoorWalkablePlane"/> MISS
|
|
/// event, dumping foot world/local position, the nearest
|
|
/// walkable polygon in the cell (with XY-containment flag and
|
|
/// vertical gap), and whether the LandCell terrain at the same
|
|
/// XY would have grounded the player.</description></item>
|
|
/// <item><description>One <c>[floor-polys]</c> line per indoor
|
|
/// cell cached, enumerating each walkable-eligible polygon's
|
|
/// id, normal Z, local-XY bounding box, and plane Z at the
|
|
/// bbox center.</description></item>
|
|
/// </list>
|
|
/// Together these answer H1 (multi-cell iteration missing) vs H2
|
|
/// (probe distance too short) vs H3 (poly absent /
|
|
/// <c>walkable_hits_sphere</c> rejection) for the ISSUES #83
|
|
/// stuck-falling bug. Spike-only — remove once the root cause is
|
|
/// identified and the fix lands.
|
|
///
|
|
/// <para>
|
|
/// Initial state from <c>ACDREAM_PROBE_WALK_MISS=1</c>.
|
|
/// No DebugPanel mirror — one-shot diagnostic.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Spec: <c>docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md</c>.
|
|
/// </para>
|
|
/// </summary>
|
|
public static bool ProbeWalkMissEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_MISS") == "1";
|
|
|
|
/// <summary>
|
|
/// Phase A6.P1 cdb probe spike (2026-05-21). When true, every BSP
|
|
/// collision response site emits a structured <c>[push-back]</c> line:
|
|
/// input/output sphere center, plane geometry, push-back delta, walk
|
|
/// interp, and the dispatcher's selected path. Direct comparison to
|
|
/// retail's cdb breakpoint set documented at
|
|
/// <c>tools/cdb/a6-probe.cdb</c>.
|
|
///
|
|
/// <para>
|
|
/// Three emission sites: <c>BSPQuery.AdjustSphereToPlane</c>
|
|
/// (the suspected over-correction site), <see cref="BSPQuery.FindCollisions"/>
|
|
/// (the 6-path dispatcher), and <see cref="Transition.CheckOtherCells"/>
|
|
/// (multi-cell BSP iteration outcomes). All three are zero-cost when
|
|
/// off — checked via early-out at each site.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Initial state from <c>ACDREAM_PROBE_PUSH_BACK=1</c>.
|
|
/// Runtime-toggleable via DebugVM mirror.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Spec: <c>docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md</c>.
|
|
/// </para>
|
|
/// </summary>
|
|
public static bool ProbePushBackEnabled { get; set; } =
|
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_PUSH_BACK") == "1";
|
|
|
|
/// <summary>
|
|
/// A6.P1 emission helper for the <c>AdjustSphereToPlane</c> site.
|
|
/// One line per call: input sphere center, plane geometry, push-back
|
|
/// delta, walk-interp before/after, and whether the adjust applied.
|
|
/// Direct paired comparison to retail's cdb breakpoint on
|
|
/// <c>CPolygon::adjust_sphere_to_plane</c>.
|
|
///
|
|
/// <para>
|
|
/// Caller MUST guard with <c>if (!ProbePushBackEnabled) return;</c>
|
|
/// before computing the delta arguments — this method assumes the
|
|
/// caller paid that price already.
|
|
/// </para>
|
|
/// </summary>
|
|
public static void LogPushBackAdjust(
|
|
Vector3 inputCenter,
|
|
Vector3 outputCenter,
|
|
Plane plane,
|
|
float radius,
|
|
float walkInterpBefore,
|
|
float walkInterpAfter,
|
|
float dpPos,
|
|
float dpMove,
|
|
float iDist,
|
|
bool applied)
|
|
{
|
|
var delta = outputCenter - inputCenter;
|
|
float deltaMag = delta.Length();
|
|
var ci = System.Globalization.CultureInfo.InvariantCulture;
|
|
Console.WriteLine(string.Format(ci,
|
|
"[push-back] site=adjust_sphere " +
|
|
"in=({0:F4},{1:F4},{2:F4}) " +
|
|
"out=({3:F4},{4:F4},{5:F4}) " +
|
|
"delta=({6:F4},{7:F4},{8:F4}) deltaMag={9:F4} " +
|
|
"n=({10:F4},{11:F4},{12:F4}) d={13:F4} " +
|
|
"r={14:F4} winterp={15:F4}->{16:F4} " +
|
|
"dpPos={17:F4} dpMove={18:F4} iDist={19:F4} applied={20}",
|
|
inputCenter.X, inputCenter.Y, inputCenter.Z,
|
|
outputCenter.X, outputCenter.Y, outputCenter.Z,
|
|
delta.X, delta.Y, delta.Z, deltaMag,
|
|
plane.Normal.X, plane.Normal.Y, plane.Normal.Z, plane.D,
|
|
radius, walkInterpBefore, walkInterpAfter,
|
|
dpPos, dpMove, iDist, applied));
|
|
}
|
|
|
|
/// <summary>
|
|
/// A6.P1 emission helper for the <c>FindCollisions</c> dispatcher
|
|
/// site. One line per call: input sphere center, movement vector,
|
|
/// path-selection state flags (collide / insertType / objState),
|
|
/// walk-interp at entry, and the return state. Direct paired
|
|
/// comparison to retail's cdb breakpoint on
|
|
/// <c>BSPTREE::find_collisions</c>.
|
|
///
|
|
/// <para>
|
|
/// Caller MUST guard with <c>if (!ProbePushBackEnabled) return;</c>
|
|
/// before calling.
|
|
/// </para>
|
|
/// </summary>
|
|
public static void LogPushBackDispatch(
|
|
Vector3 sphereCenter,
|
|
Vector3 movement,
|
|
bool collide,
|
|
int insertType,
|
|
int objState,
|
|
float walkInterpEntry,
|
|
int returnState)
|
|
{
|
|
// Output format mirrors LogPushBackAdjust: single string.Format
|
|
// with CultureInfo.InvariantCulture so the F4 / hex formatting is
|
|
// locale-independent and the output is greppable.
|
|
var ci = System.Globalization.CultureInfo.InvariantCulture;
|
|
Console.WriteLine(string.Format(ci,
|
|
"[push-back-disp] site=dispatch " +
|
|
"center=({0:F4},{1:F4},{2:F4}) " +
|
|
"mvmt=({3:F4},{4:F4},{5:F4}) " +
|
|
"collide={6} insertType={7} objState=0x{8:X} " +
|
|
"winterp={9:F4} return={10}",
|
|
sphereCenter.X, sphereCenter.Y, sphereCenter.Z,
|
|
movement.X, movement.Y, movement.Z,
|
|
collide, insertType, objState,
|
|
walkInterpEntry, returnState));
|
|
}
|
|
|
|
/// <summary>
|
|
/// A6.P1 emission helper for the <c>CheckOtherCells</c> multi-cell
|
|
/// BSP iteration site. One line per off-cell hit: from-cell, to-cell,
|
|
/// BSP result (Ok / Adjusted / Slid / Collided), and the iteration
|
|
/// outcome. Direct paired comparison to retail's
|
|
/// <c>CTransition::check_other_cells</c> loop at decomp line
|
|
/// 272717. Augments the existing A4 multi-cell BSP instrumentation
|
|
/// with explicit per-iteration outcome telemetry.
|
|
///
|
|
/// <para>
|
|
/// Caller MUST guard with <c>if (!ProbePushBackEnabled) return;</c>
|
|
/// before calling.
|
|
/// </para>
|
|
/// </summary>
|
|
public static void LogPushBackCellTransit(
|
|
uint primaryCellId,
|
|
uint otherCellId,
|
|
int bspResult,
|
|
bool halted)
|
|
{
|
|
var ci = System.Globalization.CultureInfo.InvariantCulture;
|
|
Console.WriteLine(string.Format(ci,
|
|
"[push-back-cell] site=other_cell " +
|
|
"primary=0x{0:X8} other=0x{1:X8} " +
|
|
"bspResult={2} halted={3}",
|
|
primaryCellId, otherCellId, bspResult, halted));
|
|
}
|
|
|
|
public static void LogCpBoolWrite(string field, bool oldValue, bool newValue)
|
|
{
|
|
var caller = GetCpCallerName();
|
|
Console.WriteLine(System.FormattableString.Invariant(
|
|
$"[cp-write] {field}: {oldValue} -> {newValue} caller={caller}"));
|
|
}
|
|
|
|
public static void LogCpPlaneWrite(string field, Plane oldPlane, Plane newPlane)
|
|
{
|
|
var caller = GetCpCallerName();
|
|
Console.WriteLine(System.FormattableString.Invariant(
|
|
$"[cp-write] {field}: n=({oldPlane.Normal.X:F3},{oldPlane.Normal.Y:F3},{oldPlane.Normal.Z:F3}) D={oldPlane.D:F3} -> n=({newPlane.Normal.X:F3},{newPlane.Normal.Y:F3},{newPlane.Normal.Z:F3}) D={newPlane.D:F3} caller={caller}"));
|
|
}
|
|
|
|
public static void LogCpCellIdWrite(string field, uint oldValue, uint newValue)
|
|
{
|
|
var caller = GetCpCallerName();
|
|
Console.WriteLine(System.FormattableString.Invariant(
|
|
$"[cp-write] {field}: 0x{oldValue:X8} -> 0x{newValue:X8} caller={caller}"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Walks the stack to identify the first frame outside <c>CollisionInfo</c>
|
|
/// and <c>PhysicsDiagnostics</c> — that's the actual caller writing the
|
|
/// ContactPlane field. Format: <c>TypeName.MethodName:line</c> when file
|
|
/// info is available, else just <c>TypeName.MethodName</c>. Walked with
|
|
/// <c>fileNeeded=true</c> only when the probe flag is on, so zero cost
|
|
/// when off.
|
|
/// </summary>
|
|
private static string GetCpCallerName()
|
|
{
|
|
// Skip 2: this method + the LogCp*Write helper that called it.
|
|
var st = new System.Diagnostics.StackTrace(2, fNeedFileInfo: true);
|
|
for (int i = 0; i < st.FrameCount; i++)
|
|
{
|
|
var f = st.GetFrame(i);
|
|
var m = f?.GetMethod();
|
|
if (m is null) continue;
|
|
var typeName = m.DeclaringType?.Name ?? "?";
|
|
if (typeName == "CollisionInfo" || typeName == "PhysicsDiagnostics") continue;
|
|
int line = f?.GetFileLineNumber() ?? 0;
|
|
return line > 0 ? $"{typeName}.{m.Name}:{line}" : $"{typeName}.{m.Name}";
|
|
}
|
|
return "?";
|
|
}
|
|
}
|