using System; using System.Collections.Generic; using System.Numerics; namespace AcDream.Core.Physics; /// /// 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. /// /// /// L.2d slice 1 (2026-05-13) adds + /// the diagnostic side-channel. Future /// slices may fold the older ACDREAM_DUMP_* env vars into this /// class for unified runtime toggling. Until then, those older flags /// remain sticky-at-startup per their original implementation. /// /// public static class PhysicsDiagnostics { /// /// Slice I5 graph/flat referee cadence. Zero disables the diagnostic; /// N samples every Nth collision-traversal entry. The graph path remains /// authoritative regardless of the comparison result. /// public static int CollisionShadowSampleEvery { get; set; } = ParsePositiveInt( Environment.GetEnvironmentVariable( "ACDREAM_COLLISION_SHADOW_EVERY")); /// /// Directory for deterministic Slice I5 mismatch artifacts. /// public static string CollisionShadowArtifactDirectory { get; set; } = Environment.GetEnvironmentVariable( "ACDREAM_COLLISION_SHADOW_DIR") ?? Path.Combine( Environment.CurrentDirectory, ".test-out", "collision-shadow"); /// /// When true, emits /// one structured [resolve] 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 ACDREAM_PROBE_RESOLVE=1. /// public static bool ProbeResolveEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_RESOLVE") == "1"; /// /// When true, every change to PlayerMovementController.CellId /// emits one [cell-transit] line: old → new cell, current /// world position, reason tag (resolver / teleport). /// Initial state from ACDREAM_PROBE_CELL=1. /// public static bool ProbeCellEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL") == "1"; /// /// C4 route 7 (pickup/parent/delete) connected-gate confirmation /// signal — TEMPORARY, part of the existing probe family. When true, /// one [child-cell] line is emitted per Runtime committed-child /// canonical cell write: parent guid, child guid, old and new cell, /// and a cause tag (attach / headless-attach / /// propagate / withdraw / delete). A clean-looking /// session with zero cause=propagate lines during a landblock /// crossing is a not-run, not a pass. Initial state from /// ACDREAM_PROBE_CHILD_CELL=1. /// public static bool ProbeChildCellEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CHILD_CELL") == "1"; /// /// Issue #309's connected-gate confirmation signal (2026-08-04, C4 route /// 4b-2). The gate's quiescence steps need a ForcePosition to land INSIDE /// a transient collision-prefix quiescence window, which a tester cannot /// synchronise with a teleport or portal arrival — so without a signal a /// clean teleport and a correctly-parked one look identical and the gate /// passes while broken. /// /// When true, RuntimeSetPositionState emits one /// [park] line when a placement parks (entity, park cause, the /// blocking landblock prefix if any, the caller's pre-snap cell, the /// POST-snap cell the rollback would restore into, and whether the /// rollback was captured or declined), and one [park-restore] line /// when a cancelled park's withdrawal is rolled back or its residency arm /// is declined at restore time. Low volume: parks are rare, and nothing /// is emitted for an ordinary committing placement. Zero cost when off /// (one static-bool read per park). /// /// Initial state from ACDREAM_PROBE_PARK=1. /// public static bool ProbeParkEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_PARK") == "1"; /// /// #283 reachability probe (2026-08-03). Runtime rebases its world frame /// the instant an accepted Position carries TeleportAdvanced, while App's /// LiveWorldOriginState rebases only once /// StreamingOriginRecenterCoordinator observes old-window /// retirement completion — many frames later. Between those edges the two /// owners can disagree by the source-to-destination landblock delta, and /// anything converted in that gap lands a multiple of 192 m from the /// geometry App is building. /// /// A 2026-08-03 connected run answered that question: 11 reveals /// across six landblocks spanning ~45 km recorded ZERO disagreements, /// because the recenter detaches every resident landblock before adopting /// the new origin and so serializes the two rebases. Disagreement is now /// a terminal invariant /// (LiveWorldOriginState.EnsureAgreesWithRuntimeFrame) rather than /// something to observe. /// /// When true, this now emits one verbose [world-frame] agree /// line per projected conversion recording the centre both owners used — /// what you want when investigating a placement that looks displaced but /// is NOT a frame disagreement. Measurement only; it never gates /// placement. Initial state from /// ACDREAM_PROBE_WORLD_FRAME=1. /// public static bool ProbeWorldFrameEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_WORLD_FRAME") == "1"; /// /// Stuck-cast/missing-attack investigation (2026-07-30). When true, /// every REMOTE ground-contact edge (HitGround / LeaveGround — each of /// which drains the mover's pending action animations via retail's /// HandleEnterWorld) emits one [remote-edge] line with the /// server guid. Correlates eaten attack animations with spurious /// contact flickers. Rides ACDREAM_DUMP_MOTION=1 so one flag /// captures the whole animation story. /// public static bool DumpMotionEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1"; /// /// L.2d slice 1 (2026-05-13). When true, every BSP-shadow-entry hit /// attributed by TransitionTypes.FindObjCollisions emits a /// multi-line [resolve-bldg] 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. /// /// /// Also gates a one-time [entity-source] log line at every /// ShadowObjects.Register(...) call site in GameWindow /// — makes entityId=0xA9B479 in a probe line greppable to its /// source registration within the same log file. /// /// /// /// Initial state from ACDREAM_PROBE_BUILDING=1. Mirrorable /// via DebugVM.ProbeBuilding when ACDREAM_DEVTOOLS=1. /// /// /// /// Spec: docs/superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md. /// /// public static bool ProbeBuildingEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_BUILDING") == "1"; /// /// A6.P5 (2026-05-25) — dump the cellSet that /// BuildCellSetAndPickContaining produces. One line per call: /// seed cell, sphere world XY, candidate count, and the full candidate /// list (hex). Pair with [bsp-test] / [resolve] to see /// whether the door's outdoor cell is reachable from the player's /// current indoor cell via the portal-walk. /// public static bool ProbeCellSetEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELLSET") == "1"; /// /// R5-V3 #171 residuals (2026-07-04) — sticky-melee timeline probe. /// One [sticky] line per StickyManager lifecycle event (STICK / /// UNSTICK / LEASE-EXPIRE / TARGET-status teardown) and per armed /// AdjustOffset tick (guid, signed gap distance, applied delta, /// heading delta), plus [sticky-snap-skip] lines at the NPC /// UpdatePosition handler when a server hard-snap is suppressed because /// the entity is stuck. Heavy while a pack is stuck (~60 Hz × stuck /// count); capture-session only. All lines carry the guid /// (feedback_probe_identity_attribution). /// public static bool ProbeStickyEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_STICKY") == "1"; /// /// Bug A investigation (2026-08-04, live route 4a test — see /// docs/ISSUES.md #32 and /// docs/research/2026-08-04-remote-landing-investigation.md): a /// PLAYER remote sometimes stays in the falling animation after landing, /// then snaps to the grounded pose after a delay. Three hypotheses were /// identified, with non-overlapping fixes, so this probe captures the /// state needed to discriminate them at BOTH remote landing-detection /// sites: the UpdatePosition landing block in /// LiveEntityNetworkUpdateController (site=controller) and /// the per-tick VectorUpdate landing branch in /// RuntimeRemotePhysicsUpdater (site=per-tick). /// /// /// When true, emits one [remote-landing] line per landing edge via /// , capturing: the airborne flag on entry, /// whether the Gravity state bit is still set (the /// MotionInterpreter.HitGround gate at /// MotionInterpreter.cs:~2435 no-ops silently when it is NOT — /// hypothesis 1), the Contact/OnWalkable transient bits, whether a /// DefaultSink is bound (hypothesis 2 — nothing to dispatch /// through), the per-tick site's resolveResult.IsOnGround (n/a at /// the controller site, which has no resolver call), and the /// sequencer's current style/motion id (hypothesis 3 — the sequencer /// disagrees with what the re-apply should produce). A companion /// [remote-landing-gate] line fires via /// whenever a landing site is /// reached but Gravity is already clear, so the HitGround call about to /// happen is a silent no-op — the single most valuable signal for /// hypothesis 1. /// /// /// /// Initial state from ACDREAM_PROBE_REMOTE_LANDING=1. TEMPORARY — /// strip once the discriminating live-test capture has landed. /// /// public static bool ProbeRemoteLandingEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_REMOTE_LANDING") == "1"; /// /// C4 route 4b-3 (2026-08-04) live-execution proof (process rule 5): one /// [remote-teleport] line per routed teleport arm /// (LiveEntityNetworkUpdateController.ApplyRemoteContactRouting's /// teleport dispatch), so a connected test can confirm the new arm /// actually executed rather than inferring it from a clean-looking /// session (#309's lesson — a session with zero probe lines is a /// not-run). Initial state from ACDREAM_PROBE_REMOTE_TELEPORT=1. /// TEMPORARY — strip with the rest of the probe family once the /// two-client connected teleport gate has landed. /// public static bool ProbeRemoteTeleportEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_REMOTE_TELEPORT") == "1"; /// /// Emit one [remote-teleport] line for one routed teleport arm. /// Self-guards on , so callers /// need not pre-check. is "teleport-ts" /// or "cellless" (D1's two classifier predicates); the guid, the /// hook's own currency result, and the placement status are the fields /// the connected gate recipe reads back. /// public static void LogRemoteTeleport( uint guid, string cause, bool hookRan, string placementStatus) { if (!ProbeRemoteTeleportEnabled) return; Console.WriteLine(System.FormattableString.Invariant( $"[remote-teleport] guid=0x{guid:X8} cause={cause} hookRan={hookRan} placement={placementStatus}")); } /// /// Emit one [remote-landing] line for a remote landing-detection /// edge. Caller MUST guard with /// if (!ProbeRemoteLandingEnabled) return; before calling. /// is at the /// controller site (no per-frame resolver call at that edge). /// public static void LogRemoteLanding( string site, uint guid, bool airborneBefore, bool gravitySet, bool contact, bool onWalkable, bool hasDefaultSink, bool? resolveIsOnGround, uint sequencerStyle, uint sequencerMotion) { var ci = System.Globalization.CultureInfo.InvariantCulture; string onGroundText = resolveIsOnGround.HasValue ? resolveIsOnGround.Value.ToString() : "n/a"; Console.WriteLine(string.Format(ci, "[remote-landing] site={0} guid=0x{1:X8} t={2} airborneBefore={3} " + "gravitySet={4} contact={5} onWalkable={6} hasDefaultSink={7} " + "resolveIsOnGround={8} seqStyle=0x{9:X8} seqMotion=0x{10:X8}", site, guid, Environment.TickCount64, airborneBefore, gravitySet, contact, onWalkable, hasDefaultSink, onGroundText, sequencerStyle, sequencerMotion)); } /// /// Emit one [remote-landing-gate] line when a landing edge is /// reached but the Gravity state bit is already clear, so the imminent /// MotionInterpreter.HitGround call will silently no-op (the /// gate at MotionInterpreter.cs:~2435) — hypothesis 1 for Bug A. /// Caller MUST guard with /// if (!ProbeRemoteLandingEnabled) return; before calling. /// public static void LogRemoteLandingGateNoOp(string site, uint guid) { Console.WriteLine(System.FormattableString.Invariant( $"[remote-landing-gate] site={site} guid=0x{guid:X8} t={Environment.TickCount64} NOOP gravityAlreadyClear=true")); } // ── [remote-landing-after] — the OUTCOME half of the Bug A probe ────── // // docs/research/2026-08-04-bug-a-h3-scheduler-diagnosis.md §6.1: the // [remote-landing] line above reads state immediately BEFORE // MovementManager.HitGround, so it cannot separate (a) the edge never // firing, from (b) HitGround firing and something re-asserting Falling, // from (c) the motion-table sink refusing the cycle. The companion line // below reads the same entity immediately AFTER the call, at the same // two sites, and pairs 1:1 with it (same site + guid, next line for // that guid). // // Dispatch capture: MotionTableDispatchSink.ApplyMotion discards the // MotionTableManagerError code (it returns bool) and HitGround itself // returns void, so nothing at the call site can observe what the sink // did. These [ThreadStatic] latches carry it across the synchronous // HitGround call without changing any signature: the call site calls // BeginRemoteLandingDispatchCapture() right before HitGround, the sink // records each ApplyMotion, and LogRemoteLandingAfter reports the count // plus the LAST ApplyMotion — which for the landing re-apply // (ApplyInterpretedMovement, MotionInterpreter.cs:2842-2903) is the // decisive one: either Falling (:2867) or InterpretedState.ForwardCommand // (:2878). Thread-static because the whole window is synchronous on the // ticking thread, and headless hosts tick several sessions in parallel. // // Every member here is inert unless ProbeRemoteLandingEnabled is true. // TEMPORARY — strip with the rest of the ACDREAM_PROBE_REMOTE_LANDING // family once the discriminating live capture has landed. [ThreadStatic] private static int _remoteLandingApplyCalls; [ThreadStatic] private static uint _remoteLandingLastApplyMotion; [ThreadStatic] private static uint _remoteLandingLastApplyResult; /// /// Arm the per-call sink-dispatch capture read back by /// . Call immediately before /// MovementManager.HitGround. No-op unless /// . /// public static void BeginRemoteLandingDispatchCapture() { if (!ProbeRemoteLandingEnabled) return; _remoteLandingApplyCalls = 0; _remoteLandingLastApplyMotion = 0; _remoteLandingLastApplyResult = 0; } /// /// Record one IInterpretedMotionSink.ApplyMotion dispatch and its /// raw MotionTableManagerError code. Called by /// ; self-guarded, so it is /// a single flag test when the probe is off. /// public static void RecordRemoteLandingDispatch(uint motion, uint result) { if (!ProbeRemoteLandingEnabled) return; _remoteLandingApplyCalls++; _remoteLandingLastApplyMotion = motion; _remoteLandingLastApplyResult = result; } /// /// Emit one [remote-landing-after] line for the landing edge whose /// [remote-landing] line was just written. Caller MUST guard with /// if (!ProbeRemoteLandingEnabled) return; before calling, and MUST /// emit it before any post-HitGround ownership re-check can return — a /// before-line with no after-line therefore means the call site threw. /// is if a /// gate short-circuited between the two lines (no such gate exists at /// either site today; the field exists so the absence is stated rather /// than inferred from a missing line). /// public static void LogRemoteLandingAfter( string site, uint guid, bool hitGroundInvoked, uint sequencerStyle, uint sequencerMotion, uint forwardCommand) { var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[remote-landing-after] site={0} guid=0x{1:X8} t={2} " + "hitGroundInvoked={3} seqStyle=0x{4:X8} seqMotion=0x{5:X8} " + "fwdCmd=0x{6:X8} sinkApplyCalls={7} sinkLastMotion=0x{8:X8} " + "sinkLastResult=0x{9:X8}", site, guid, Environment.TickCount64, hitGroundInvoked, sequencerStyle, sequencerMotion, forwardCommand, _remoteLandingApplyCalls, _remoteLandingLastApplyMotion, _remoteLandingLastApplyResult)); } // ── [remote-slide-*] — Bug B (remote ledge/roof slide) capture ──────── // // docs/research/2026-08-04-bug-b-remote-slide-diagnosis.md §2 names TWO // live blip producers and states that NO existing probe distinguishes // them: // • Candidate 1 — AP-87's `bodyToTarget > 4 m` body snap in // RuntimeRemoteSteadyStatePosition.ApplyInterpolate (:129-137). // • Candidate 2 — InterpolationManager's own retail-faithful // `node_fail_counter > 3` snap-to-tail (:458-467; retail // InterpolationManager::UseTime @0x00555f20). That code is CORRECT // and is only reachable because §1 froze the body; this probe // OBSERVES it and must never be read as a reason to change it. // Both emit `[remote-slide-snap]` with a distinct `producer=` tag, so // one grep finds every blip and the tag alone answers "which one". // // The same family also settles the diagnosis's two load-bearing NOT // ESTABLISHED items: // • #1 (Shape A vs Shape B) — `[remote-slide-up] wireGrounded=` is the // raw ACE PositionFlags.IsGrounded bit for the accepted packet, // emitted at the ONE routing point both remote arms pass through, // AHEAD of the NoPositionOperation early returns, so a Shape-A slide // (every packet `wireGrounded=false disp=NoPositionOperation`) is // visible even though acdream writes nothing for it. // • #2 (is the roof steep in OUR collision data) — // `[remote-slide-tick] bodyCpNz=/rsCpNz=` against `floorZ=`. // `[remote-slide-vec]` covers the 0xF74E half of NOT ESTABLISHED #4: an // absence of lines during a slide is itself the answer. // // Pure reads only. Nothing here gates, orders, or mutates production // state; the throttle dictionary and the attribution latch are // probe-owned and [ThreadStatic] because a headless host ticks several // sessions in parallel. // // TEMPORARY — strip the whole ACDREAM_PROBE_REMOTE_SLIDE family once the // two-client roof capture has landed. private static readonly string? RemoteSlideProbeRaw = Environment.GetEnvironmentVariable("ACDREAM_PROBE_REMOTE_SLIDE"); /// /// Initial state from ACDREAM_PROBE_REMOTE_SLIDE. 1 enables /// the family for every remote; a comma-separated hex GUID list (e.g. /// 0x50000123,0x8001ABCD) enables it only for those GUIDs, which is /// what keeps a live two-client capture readable. Unset/empty = inert. /// public static bool ProbeRemoteSlideEnabled { get; set; } = !string.IsNullOrWhiteSpace(RemoteSlideProbeRaw); /// /// Optional GUID allow-list for . /// Empty means "every remote". /// public static IReadOnlySet ProbeRemoteSlideGuids { get; set; } = RemoteSlideProbeRaw is null || RemoteSlideProbeRaw.Trim() == "1" ? new HashSet() : ParseHexIdList(RemoteSlideProbeRaw); /// /// The single gate every [remote-slide-*] call site checks first. /// One static bool read plus (only when enabled) one set lookup. /// public static bool ShouldLogRemoteSlide(uint guid) => ProbeRemoteSlideEnabled && (ProbeRemoteSlideGuids.Count == 0 || ProbeRemoteSlideGuids.Contains(guid)); // Neither InterpolationManager nor RuntimeRemoteSteadyStatePosition has // access to a server GUID (RemoteMotion does not carry one), and // claude-memory/feedback_probe_identity_attribution.md makes the GUID // mandatory on a per-entity probe. Rather than widen either production // signature, the two per-remote windows that call into them stamp this // latch first — the same [ThreadStatic] shape the [remote-landing-after] // dispatch capture already uses, and for the same reason (the whole // window is synchronous on the ticking thread). [ThreadStatic] private static uint _remoteSlideAttributionGuid; /// /// Stamp the GUID that any [remote-slide-*] line emitted from /// inside the following synchronous per-remote window belongs to. No-op /// unless . /// public static void BeginRemoteSlideAttribution(uint guid) { if (!ProbeRemoteSlideEnabled) return; _remoteSlideAttributionGuid = guid; } /// The GUID stamped by the innermost /// ; 0 when unknown. public static uint RemoteSlideAttributionGuid => _remoteSlideAttributionGuid; /// /// Per-GUID rate limit for the ~30 Hz [remote-slide-tick] line. /// A resting remote emits at most one line per this interval; any change /// in the caller-supplied signature (the contact/walkable/airborne/ /// gravity/steep/moved bit pattern) emits immediately, so every /// transition is captured at full fidelity. /// private const long RemoteSlideTickThrottleMs = 200; [ThreadStatic] private static Dictionary? _remoteSlideTickGate; /// /// Edge-or-throttle admission for . /// Returns true when the line should be emitted; updates the per-GUID /// gate as a side effect. Probe-owned state only. /// public static bool ShouldEmitRemoteSlideTick(uint guid, int signature) { if (!ShouldLogRemoteSlide(guid)) return false; _remoteSlideTickGate ??= new Dictionary(); long now = Environment.TickCount64; if (_remoteSlideTickGate.TryGetValue(guid, out var previous) && previous.Signature == signature && now - previous.Ms < RemoteSlideTickThrottleMs) { return false; } _remoteSlideTickGate[guid] = (now, signature); return true; } /// /// One [remote-slide-up] line per accepted remote Position, from /// the single point BOTH remote arms pass through — ahead of the /// NoPositionOperation early returns, so a Shape-A slide (which /// acdream answers by writing nothing) still produces a line. /// Caller MUST guard with . /// public static void LogRemoteSlideUp( uint guid, bool wireGrounded, Vector3? wireVelocity, string disposition, float? playerDistance, float bodyToTarget, float bodySnapThreshold, bool willBeDrTicked, // Read at packet ENTRY. The player-remote arm stamps // LastServerPosTime between here and the routing call, so the value // ApplyInterpolate actually tests can differ — the // [remote-slide-snap] producer=ap87-4m line reports that one. Hence // the distinct firstUpAtEntry= field name. bool firstUp, bool airborne, bool contact, bool onWalkable, bool gravity, Vector3 bodyVelocity, bool contactPlaneValid, float contactPlaneNormalZ, Vector3 wirePosition, Vector3 bodyPosition, int interpQueueDepth, int interpFailCount) { var ci = System.Globalization.CultureInfo.InvariantCulture; string wireVel = wireVelocity is { } wv ? string.Format(ci, "({0:F3},{1:F3},{2:F3})", wv.X, wv.Y, wv.Z) : "null"; string playerDist = playerDistance is { } pd ? pd.ToString("F2", ci) : "n/a"; Console.WriteLine(string.Format(ci, "[remote-slide-up] guid=0x{0:X8} t={1} wireGrounded={2} wireVel={3} " + "disp={4} playerDist={5} bodyToTarget={6:F3} snapThreshold={7:F3} " + "willBeDrTicked={8} firstUpAtEntry={9} airborne={10} contact={11} " + "onWalkable={12} gravity={13} bodyVel=({14:F3},{15:F3},{16:F3}) " + "cpValid={17} cpNz={18:F4} floorZ={19:F4} steep={20} " + "wirePos=({21:F3},{22:F3},{23:F3}) bodyPos=({24:F3},{25:F3},{26:F3}) " + "queueDepth={27} failCount={28}", guid, Environment.TickCount64, wireGrounded, wireVel, disposition, playerDist, bodyToTarget, bodySnapThreshold, willBeDrTicked, firstUp, airborne, contact, onWalkable, gravity, bodyVelocity.X, bodyVelocity.Y, bodyVelocity.Z, contactPlaneValid, contactPlaneNormalZ, PhysicsGlobals.FloorZ, contactPlaneValid && contactPlaneNormalZ < PhysicsGlobals.FloorZ, wirePosition.X, wirePosition.Y, wirePosition.Z, bodyPosition.X, bodyPosition.Y, bodyPosition.Z, interpQueueDepth, interpFailCount)); } /// /// One [remote-slide-vec] line per accepted remote 0xF74E /// VectorUpdate. NOT ESTABLISHED #4 asks whether ACE relays one at all /// during a slide — the ABSENCE of these lines across a captured slide /// window is the answer, which is why this sits on the committed path /// rather than inside the Velocity.Z > 0.5f airborne branch. /// Caller MUST guard with . /// public static void LogRemoteSlideVector( uint guid, Vector3 wireVelocity, Vector3 wireOmega, bool willMarkAirborne, bool airborneBefore, bool contact, bool onWalkable, bool gravity, Vector3 bodyVelocity, bool contactPlaneValid, float contactPlaneNormalZ) { var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[remote-slide-vec] guid=0x{0:X8} t={1} " + "wireVel=({2:F3},{3:F3},{4:F3}) wireOmega=({5:F3},{6:F3},{7:F3}) " + "willMarkAirborne={8} airborneBefore={9} contact={10} " + "onWalkable={11} gravity={12} bodyVel=({13:F3},{14:F3},{15:F3}) " + "cpValid={16} cpNz={17:F4} floorZ={18:F4} steep={19}", guid, Environment.TickCount64, wireVelocity.X, wireVelocity.Y, wireVelocity.Z, wireOmega.X, wireOmega.Y, wireOmega.Z, willMarkAirborne, airborneBefore, contact, onWalkable, gravity, bodyVelocity.X, bodyVelocity.Y, bodyVelocity.Z, contactPlaneValid, contactPlaneNormalZ, PhysicsGlobals.FloorZ, contactPlaneValid && contactPlaneNormalZ < PhysicsGlobals.FloorZ)); } /// /// Blip producer Candidate 1 — AP-87's bodyToTarget > 4 m /// body snap (RuntimeRemoteSteadyStatePosition.ApplyInterpolate). /// Tagged producer=ap87-4m. Caller MUST guard with /// . /// public static void LogRemoteSlideBodySnap( uint guid, bool firstUp, bool willBeDrTicked, float bodyToTarget, float threshold, Vector3 bodyPosition, Vector3 targetPosition, int interpQueueDepth, int interpFailCount) { var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[remote-slide-snap] producer=ap87-4m guid=0x{0:X8} t={1} " + "firstUp={2} willBeDrTicked={3} bodyToTarget={4:F3} threshold={5:F3} " + "body=({6:F3},{7:F3},{8:F3}) target=({9:F3},{10:F3},{11:F3}) " + "queueDepth={12} failCount={13}", guid, Environment.TickCount64, firstUp, willBeDrTicked, bodyToTarget, threshold, bodyPosition.X, bodyPosition.Y, bodyPosition.Z, targetPosition.X, targetPosition.Y, targetPosition.Z, interpQueueDepth, interpFailCount)); } /// /// The non-blip outcome of the same seam: the packet fed the queue. Its /// presence is what tells Shape B (queue fed, so Candidate 2 can arm) /// apart from Shape A (queue never fed, so only Candidate 1 can fire). /// Caller MUST guard with . /// public static void LogRemoteSlideEnqueue( uint guid, float bodyToTarget, Vector3 targetPosition, int interpQueueDepth, int interpFailCount) { var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[remote-slide-enq] guid=0x{0:X8} t={1} bodyToTarget={2:F3} " + "target=({3:F3},{4:F3},{5:F3}) queueDepth={6} failCount={7}", guid, Environment.TickCount64, bodyToTarget, targetPosition.X, targetPosition.Y, targetPosition.Z, interpQueueDepth, interpFailCount)); } /// /// Blip producer Candidate 2 — the retail-faithful /// node_fail_counter > 3 snap-to-tail inside /// (retail /// InterpolationManager::UseTime @0x00555f20). Tagged /// producer=interp-stall. This line is OBSERVATION ONLY: the code /// it reports on is a correct port firing correctly on a body frozen /// upstream, and the diagnosis explicitly rules it out of scope for any /// fix. Self-guarded on so the /// snap site pays one bool read when off; GUID comes from /// . /// public static void LogRemoteSlideStallSnap( int failCount, int threshold, int queueDepth, Vector3 bodyPosition, Vector3 tailPosition, float distanceToHead) { uint guid = _remoteSlideAttributionGuid; if (!ShouldLogRemoteSlide(guid)) return; Vector3 tailDelta = tailPosition - bodyPosition; var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[remote-slide-snap] producer=interp-stall guid=0x{0:X8} t={1} " + "failCount={2} threshold={3} queueDepth={4} " + "body=({5:F3},{6:F3},{7:F3}) tail=({8:F3},{9:F3},{10:F3}) " + "tailDelta=({11:F3},{12:F3},{13:F3}) tailDeltaLen={14:F3} " + "distToHead={15:F3}", guid, Environment.TickCount64, failCount, threshold, queueDepth, bodyPosition.X, bodyPosition.Y, bodyPosition.Z, tailPosition.X, tailPosition.Y, tailPosition.Z, tailDelta.X, tailDelta.Y, tailDelta.Z, tailDelta.Length(), distanceToHead)); } /// /// One [remote-slide-tick] line per admitted remote physics tick /// (see for the edge-or-throttle /// rule). Confirms LIVE what the diagnosis asserts from source. /// /// /// The first three parameters were written against the PRE-FIX code and /// their meaning changed with it; they keep their C# names because the /// diagnosis doc quotes them, but they are emitted under different LOG /// keys (see the format string). and /// once meant "the per-tick /// TransientState |= Contact | OnWalkable force flipped a bit that /// was clear" (Link 1); that force is deleted, and they now report the /// INVERSE fact — the body entered this tick WITHOUT that transient — and /// are logged as entryNoContact=/entryNoWalkable=. /// once named the vector the /// per-tick Body.Velocity = Zero discarded (Link 2); nothing /// discards it now, so it is simply the velocity the tick started with. /// /// /// /// The rs* fields are the sweep's own retail classification, which /// pre-fix the visible tick never committed (Link 4) and post-fix must /// agree with the contact=/onWalkable= columns beside them. /// vs floorZ settles NOT /// ESTABLISHED #2. /// /// Caller MUST guard with . /// public static void LogRemoteSlideTick( uint guid, bool airborne, bool forcedContact, bool forcedWalkable, Vector3 velocityBeforeZero, bool resolved, bool resolveInContact, bool resolveOnWalkable, bool resolveIsOnGround, bool resolveContactPlaneValid, float resolveContactPlaneNormalZ, bool bodyContactPlaneValid, float bodyContactPlaneNormalZ, bool contact, bool onWalkable, bool gravity, Vector3 velocity, Vector3 acceleration, Vector3 preIntegratePosition, Vector3 postIntegratePosition, Vector3 resolvedPosition) { var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, // n1 (2026-08-04): the two keys below USED to read // "forcedContact/forcedWalkable" and meant "the deleted per-tick // force flipped a clear bit". The force is gone; the same // expressions now mean the body ENTERED the tick WITHOUT that // transient — the exact inverse of what the old name implied. The // log keys are renamed so a post-fix capture cannot be misread // against a pre-fix one; the C# parameter names are unchanged // because the diagnosis doc quotes them. "[remote-slide-tick] guid=0x{0:X8} t={1} airborne={2} " + "entryNoContact={3} entryNoWalkable={4} " + "velBeforeZero=({5:F3},{6:F3},{7:F3}) resolved={8} " + "rsInContact={9} rsOnWalkable={10} rsIsOnGround={11} " + "rsCpValid={12} rsCpNz={13:F4} " + "bodyCpValid={14} bodyCpNz={15:F4} floorZ={16:F4} steep={17} " + "contact={18} onWalkable={19} gravity={20} " + "vel=({21:F3},{22:F3},{23:F3}) accel=({24:F3},{25:F3},{26:F3}) " + "pre=({27:F3},{28:F3},{29:F3}) post=({30:F3},{31:F3},{32:F3}) " + "out=({33:F3},{34:F3},{35:F3}) moved={36:F4}", guid, Environment.TickCount64, airborne, forcedContact, forcedWalkable, velocityBeforeZero.X, velocityBeforeZero.Y, velocityBeforeZero.Z, resolved, resolveInContact, resolveOnWalkable, resolveIsOnGround, resolveContactPlaneValid, resolveContactPlaneNormalZ, bodyContactPlaneValid, bodyContactPlaneNormalZ, PhysicsGlobals.FloorZ, bodyContactPlaneValid && bodyContactPlaneNormalZ < PhysicsGlobals.FloorZ, contact, onWalkable, gravity, velocity.X, velocity.Y, velocity.Z, acceleration.X, acceleration.Y, acceleration.Z, preIntegratePosition.X, preIntegratePosition.Y, preIntegratePosition.Z, postIntegratePosition.X, postIntegratePosition.Y, postIntegratePosition.Z, resolvedPosition.X, resolvedPosition.Y, resolvedPosition.Z, Vector3.Distance(preIntegratePosition, resolvedPosition))); } public static void LogCellSetBuild( uint seedCellId, System.Numerics.Vector3 sphereCenter, System.Collections.Generic.IReadOnlyCollection cellSet) { if (!ProbeCellSetEnabled) return; var ids = new System.Text.StringBuilder(); bool first = true; foreach (uint id in cellSet) { if (!first) ids.Append(','); ids.Append(System.FormattableString.Invariant($"0x{id:X8}")); first = false; } Console.WriteLine(System.FormattableString.Invariant( $"[cellset-build] seed=0x{seedCellId:X8} sphere=({sphereCenter.X:F3},{sphereCenter.Y:F3},{sphereCenter.Z:F3}) count={cellSet.Count} ids={ids}")); } /// /// L.2d slice 1 (2026-05-13). Diagnostic side-channel: the /// that /// recorded for the most recent collision-normal write. /// clears this to /// before each shadow-entry test and reads it /// back after, so emitting the [resolve-bldg] probe line can /// reference the actual hit poly without plumbing an out-param /// through BSPQuery's recursive private methods. /// /// /// Written by only when /// is true, so this stays /// zero-cost in normal play. Cylinder collisions leave this /// — the probe line emits /// hitPoly: n/a (cylinder) in that case. /// /// /// /// Not threadsafe — physics runs on a single thread. If that /// changes, this needs [ThreadStatic] 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. /// /// public static ResolvedPolygon? LastBspHitPoly { get; set; } /// /// 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 [autowalk-*] logs: /// /// [autowalk-out] on every SendUse /// / SendPickUp the local player issues — these are the /// packets that may trigger ACE's server-side CreateMoveToChain /// when the target is out of WithinUseRadius. /// [autowalk-mt] on every inbound /// UpdateMotion for the local player — captures the /// MovementType + MoveToPath + speed/runRate ACE sends. /// [autowalk-up] on every inbound /// UpdatePosition for the local player — answers "what's /// ACE's broadcast cadence during auto-walk?" /// /// Initial state from ACDREAM_PROBE_AUTOWALK=1. /// /// /// Spec: docs/superpowers/specs/2026-05-14-phase-b6-design.md /// §"Required investigation". /// /// public static bool ProbeAutoWalkEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_AUTOWALK") == "1"; /// /// 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. /// /// /// 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". /// /// /// Toggle via env var ACDREAM_PROBE_USEABILITY_FALLBACK=1 /// or DebugPanel checkbox. /// public static bool ProbeUseabilityFallbackEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_USEABILITY_FALLBACK") == "1"; /// /// L.4-diag (2026-04-30) → promoted into /// 2026-05-16 per CLAUDE.md "Code Structure Rules" §5 (diagnostic owner /// classes, not per-call-site env reads). Gates the [steep-roof] /// trace family that fires from four sites during the rooftop-bounce /// investigation: /// /// PhysicsEngine.ResolveWithTransition — /// [steep-roof] KILL-VELOCITY-APPLIED when retail-faithful /// kill_velocity zeroes the body's velocity on steep-slope /// impact. /// TransitionTypes (FindEnvCollisions /// post-step) — per-frame plane-normal trace on the active /// . /// PlayerMovementController — two sites /// emitting [steep-roof] + the per-frame bounce trace when /// the post-collision velocity disagrees with retail. /// /// Initial state from ACDREAM_DUMP_STEEP_ROOF=1. Runtime-toggleable /// via the property setter; not yet wired to a DebugPanel checkbox (open /// follow-up if a debugging session calls for it). /// public static bool DumpSteepRoofEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_DUMP_STEEP_ROOF") == "1"; /// /// Indoor walking Phase 1 (2026-05-19). When true, emits one /// [indoor-bsp] line per /// call made from 's indoor /// cell-BSP branch. Captures the cell id, sphere local position, /// resulting , 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). /// /// /// While true, this also un-gates the diagnostic /// side-channel inside /// — see the OR'd condition at every poly /// write site. Zero-cost when off. /// /// /// /// Initial state from ACDREAM_PROBE_INDOOR_BSP=1. /// Runtime-toggleable via DebugPanel. /// /// /// /// Spec: docs/superpowers/specs/2026-05-19-indoor-walking-phase1-bsp-cluster-design.md. /// /// public static bool ProbeIndoorBspEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_INDOOR_BSP") == "1"; /// /// Indoor walking Phase D follow-up (2026-05-19). When true, emits one /// [cell-cache] line each time /// caches a new EnvCell. Reports per-cell polygon counts and BSP root /// structure so the caller can cross-reference with [indoor-bsp] /// lines to distinguish between: /// /// Empty data (physicsPolyCount=0 or resolvedCount=0) /// — candidate (a)/(c) in the poly=n/a investigation. /// 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. /// 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). /// /// 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. /// /// Initial state from ACDREAM_PROBE_CELL_CACHE=1. /// public static bool ProbeCellCacheEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL_CACHE") == "1"; /// /// ContactPlane retention spike (2026-05-20). When true, every write to /// CollisionInfo.ContactPlane{,Valid,CellId,IsWater} and /// LastKnownContactPlane{,Valid,CellId,IsWater} emits one /// [cp-write] 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 /// FindEnvCollisions indoor branch is rewriting CP every frame /// instead of retaining it across frames. /// /// /// Only logs when the value actually changes (suppresses no-op writes to /// reduce log volume). Initial state from /// ACDREAM_PROBE_CONTACT_PLANE=1. Spike-only — remove once the fix /// lands and the diagnostic value is captured. /// /// public static bool ProbeContactPlaneEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CONTACT_PLANE") == "1"; /// /// Indoor walking ISSUES #83 H-disambiguation spike (2026-05-21). /// When true, two diagnostic emissions activate: /// /// One [walk-miss] line per /// 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. /// One [floor-polys] 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. /// /// Together these answer H1 (multi-cell iteration missing) vs H2 /// (probe distance too short) vs H3 (poly absent / /// walkable_hits_sphere rejection) for the ISSUES #83 /// stuck-falling bug. Spike-only — remove once the root cause is /// identified and the fix lands. /// /// /// Initial state from ACDREAM_PROBE_WALK_MISS=1. /// No DebugPanel mirror — one-shot diagnostic. /// /// /// /// Spec: docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md. /// /// public static bool ProbeWalkMissEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_MISS") == "1"; /// /// Phase A6.P1 cdb probe spike (2026-05-21). When true, every BSP /// collision response site emits a structured [push-back] 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 /// tools/cdb/a6-probe.cdb. /// /// /// Three emission sites: BSPQuery.AdjustSphereToPlane /// (the suspected over-correction site), /// (the 6-path dispatcher), and /// (multi-cell BSP iteration outcomes). All three are zero-cost when /// off — checked via early-out at each site. /// /// /// /// Initial state from ACDREAM_PROBE_PUSH_BACK=1. /// Runtime-toggleable via DebugVM mirror. /// /// /// /// Spec: docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md. /// /// public static bool ProbePushBackEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_PUSH_BACK") == "1"; /// /// A6.P3 slice 4 investigation (2026-05-22) — dumps the polygon's /// vertices + plane + sidesType + cell id whenever a push-back fires. /// Lets us compare our extracted polygon (from our cache) against /// WorldBuilder's straight-from-dat read for the same poly index, to /// falsify the "is our dat-read producing wrong polygon geometry?" /// hypothesis for issue #98 (cellar-up stuck at top step). /// /// /// Initial state from ACDREAM_PROBE_POLY_DUMP=1. /// Heavy output (one dump per AdjustSphereToPlane call); use briefly /// to capture a specific scenario, then turn off. /// /// public static bool ProbePolyDumpEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_POLY_DUMP") == "1"; /// /// Emit one [poly-dump] line with the full polygon geometry: /// cell id, polygon index, num vertices, sides flag, plane normal+D, /// and all vertex coordinates. Used in conjunction with the push-back /// probe to identify which dat polygon a push-back hit so we can /// cross-reference against WorldBuilder's straight-from-dat read. /// /// Caller MUST guard with if (!ProbePolyDumpEnabled) return;. /// public static void LogPolyDump(uint cellId, ResolvedPolygon poly) { var ci = System.Globalization.CultureInfo.InvariantCulture; var sb = new System.Text.StringBuilder(256); sb.AppendFormat(ci, "[poly-dump] cell=0x{0:X8} polyId=0x{1:X4} numPts={2} sides={3} " + "n=({4:F6},{5:F6},{6:F6}) d={7:F6} verts=[", cellId, poly.Id, poly.NumPoints, poly.SidesType, poly.Plane.Normal.X, poly.Plane.Normal.Y, poly.Plane.Normal.Z, poly.Plane.D); for (int i = 0; i < poly.Vertices.Length; i++) { if (i > 0) sb.Append(','); sb.AppendFormat(ci, "({0:F6},{1:F6},{2:F6})", poly.Vertices[i].X, poly.Vertices[i].Y, poly.Vertices[i].Z); } sb.Append(']'); Console.WriteLine(sb.ToString()); } /// /// A6.P3 slice 5 placement-insert investigation (2026-05-22). One /// [place-fail] line per Path 1 (Placement/Ethereal) call in /// BSPQuery.FindCollisions that returns Collided, plus one per /// Transition.DoStepDown placement_insert that rejects. /// /// /// Investigation target: issue #98 cellar-up stuck. The 2026-05-22 /// handoff diagnosed BSPQuery path-selection (Path 5 vs Path 6) as /// the divergence, but cross-referencing the retail cdb capture /// (every BP4 hit shows collide=0) showed retail enters the /// same Contact branch we do. The actual divergence is downstream: /// our DoStepUp's step-down probe lifts the sphere onto the cellar /// ramp, then placement_insert rejects, step_up returns failure, /// step_up_slide fires, contact-recovery loops forever. This probe /// identifies which polygon (or solid leaf) causes the placement /// reject so we know what geometry is blocking the lifted position. /// /// /// /// Initial state from ACDREAM_PROBE_PLACEMENT_FAIL=1. /// Low volume — only fires on actual rejection (one line per /// Collided return from Path 1, plus one per DoStepDown placement /// failure). Safe to leave on during a full scen4 cellar-up capture. /// /// public static bool ProbePlacementFailEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_PLACEMENT_FAIL") == "1"; /// /// Phase W Stage 0 (2026-06-02): one [cell-swept] line per /// call — the /// transition's swept cell (sp.CurCellId/sp.CheckCellId) /// vs the position-derived cell the legacy static /// path used. Proves the swept /// cell is stable where the static one strobes at the doorway boundary. /// /// /// Initial state from ACDREAM_PROBE_SWEPT=1. Zero cost when off. /// /// public static bool ProbeSweptEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_SWEPT") == "1"; // ----------------------------------------------------------------------- // #334 broadphase / candidate-disposition probe — TEMPORARY (2026-08-06) // // STRIP THIS WHOLE REGION together with the rest of the physics probe // family once #334 is scored. // // #334 is "large static formations can be walked through on flat ground, // and jumping over them drops you inside". Three candidate mechanisms // survive the report, and this probe exists to DISCRIMINATE them, not to // confirm any one of them: // // (a) the object IS a candidate in the cell but the broadphase reach // filter rejects it before its BSP is consulted (AP-158 / #333); // (b) the object is NOT in the cell's candidate set at all — a // membership / registration failure (AP-156's territory, which did // not fix this, or the static publication path never registered it); // (c) the object IS a candidate and IS NOT rejected, but resolves to no // usable shape — an empty shape list or an unresolved physics BSP. // // Context for (a): the filter measures |currPos - obj.Position| — the part // ORIGIN — against obj.Radius (the physics-BSP ROOT sphere radius) plus a // 2 m acdream-invented slack. The BSP root sphere's CENTRE is frequently // NOT the part origin (376 of 973 installed physics-BSP parts sit further // from it than half their own radius; worst 20.762 m). Where that offset // exceeds the slack, geometry well inside the sphere is rejected. So every // candidate line carries BOTH distances and the decisive // wouldAcceptAtCenter boolean. // // Context for (c): AP-152 (4abd1b5e) made us emit BSP shapes exclusively // where primitives were also emitted before. That did NOT cause #334 (the // user reproduced on a pre-AP-152 build), but the same failure mode can // exist independently, so no-shape is a first-class disposition here. // ----------------------------------------------------------------------- /// /// #334 candidate-disposition probe (2026-08-06 — TEMPORARY, strip with /// the physics-probe family). Emits two line types from /// Transition.FindObjCollisionsInCell: /// /// /// [reach-q] — one per-cell query summary: the number of /// shadow entries the cell yielded and the per-disposition tallies. /// It is emitted even when the cell yields zero entries, which is /// what makes outcome (b) visible: "cell yielded 0" at a spot where a /// formation is plainly in front of the player is a registration gap, /// and is recorded as data rather than as silence. Without this line an /// absence of rejection lines would be ambiguous between "nothing was /// rejected" and "nothing was there", which is precisely the /// unfalsifiable-criterion trap this campaign has already been bitten /// by. /// [reach-obj] — one per candidate, carrying its identity /// (mover guid, target entity id, GfxObj id, cell) and its /// disposition: exempt-self, exempt-missile, /// rejected-reach, exempt-rule, /// exempt-ethereal-stepdown, no-shape, /// bsp-only-skip, or tested:<result>. For BSP /// candidates it also carries the origin-measured distance the filter /// used, the centre-measured distance it should have used, the budget, /// the shortfall, and wouldAcceptAtCenter. /// /// /// /// Volume control (this site is hot — it runs per cell per transitional /// insert, and one resolve performs many inserts). [reach-obj] is /// de-duplicated per (mover, target, cell) and re-emits immediately /// whenever the disposition changes or the shortfall crosses a 0.5 m /// bucket, and otherwise at most once per second. [reach-q] is /// de-duplicated per (mover, cell) on the full tally tuple, so any change /// in what the cell yielded emits at once, and otherwise at most twice a /// second. Both therefore emit eagerly on change — which is exactly when /// the player walks into the formation — and stay quiet when nothing is /// happening. Nothing is aggregated away: every distinct state the query /// passes through appears. /// /// /// /// Initial state from ACDREAM_PROBE_REACH=1. Zero cost when off /// (one static bool read per query). /// /// public static bool ProbeReachEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_REACH") == "1"; private static readonly object _reachGate = new(); private static readonly Dictionary<(uint Mover, uint Entity, uint Cell), (long Ms, string Disp, int Bucket)> _reachSeenObj = new(); private static readonly Dictionary<(uint Mover, uint Cell), (long Ms, long Tally)> _reachSeenQuery = new(); /// /// One [reach-obj] line. Self-guards on /// . /// /// The moving entity's guid — never omitted; a /// per-entity probe without an identity produced a wrong root cause once /// already (feedback_probe_identity_attribution). /// What happened to this candidate. Use the /// documented vocabulary on . /// What the reach filter measured: the distance /// from the swept sphere's current centre to the target's part ORIGIN. /// Negative when not applicable. /// What it should have measured: the distance to /// the target's physics-BSP root sphere CENTRE. Negative when not /// applicable. /// The filter's admission threshold, 2 m slack /// included. /// The same threshold WITHOUT the slack — the /// honest conservative bound once the real centre is used. public static void LogReachCandidate( uint moverId, uint entityId, uint gfxObjId, uint cellId, ShadowCollisionType shape, string disposition, bool stepDown, float distOrigin, float distCenter, float objRadius, float sphereRadius, float movementLen, float budget, float centerBudget, Vector3 objPos, Vector3 bspCentreOffset, Vector3 currPos) { if (!ProbeReachEnabled) return; float shortfall = distOrigin - budget; bool wouldAcceptAtCenter = distCenter >= 0f && distCenter <= centerBudget; int bucket = distOrigin < 0f ? 0 : (int)MathF.Floor(shortfall / 0.5f); long now = Environment.TickCount64; lock (_reachGate) { var key = (moverId, entityId, cellId); if (_reachSeenObj.TryGetValue(key, out var prev) && string.Equals(prev.Disp, disposition, StringComparison.Ordinal) && prev.Bucket == bucket && now - prev.Ms < 1000) { return; } _reachSeenObj[key] = (now, disposition, bucket); } Console.WriteLine(string.Format( System.Globalization.CultureInfo.InvariantCulture, "[reach-obj] mover=0x{0:X8} obj=0x{1:X8} gfx=0x{2:X8} cell=0x{3:X8} " + "disp={4} shape={5} stepDown={6} distOrigin={7:F3} distCenter={8:F3} " + "objR={9:F3} sphereR={10:F3} move={11:F3} budget={12:F3} " + "centerBudget={13:F3} shortfall={14:F3} wouldAcceptAtCenter={15} " + "objPos=({16:F2},{17:F2},{18:F2}) " + "bspCentreOffset=({19:F2},{20:F2},{21:F2}) |bspCentreOffset|={22:F3} " + "currPos=({23:F2},{24:F2},{25:F2}) t={26}", moverId, entityId, gfxObjId, cellId, disposition, shape, stepDown, distOrigin, distCenter, objRadius, sphereRadius, movementLen, budget, centerBudget, shortfall, wouldAcceptAtCenter, objPos.X, objPos.Y, objPos.Z, bspCentreOffset.X, bspCentreOffset.Y, bspCentreOffset.Z, bspCentreOffset.Length(), currPos.X, currPos.Y, currPos.Z, now)); } /// /// One [reach-q] per-cell query summary. MUST be called even when /// the cell yields zero entries — that is the whole point of the line. /// Self-guards on . /// /// Shadow entries the cell yielded, before any /// exemption. Zero here at a spot with visible geometry is outcome (b). /// Candidates that survived the exemptions and were /// measured by the reach filter. /// Of those, how many the reach filter /// rejected — outcome (a). /// Candidates that passed the filter but resolved to /// no usable shape — outcome (c). /// Candidates that actually reached a shape test. public static void LogReachQuery( uint moverId, uint cellId, bool stepDown, int inCell, int exempt, int reached, int rejectedReach, int noShape, int tested, int blocked, Vector3 currPos) { if (!ProbeReachEnabled) return; // Tally fingerprint: any change in what this cell yielded re-emits at // once. Deliberately includes every counter, so a state the query // passes through cannot be swallowed by the throttle. long tally = (((long)inCell * 31 + exempt) * 31 + reached) * 31; tally = ((tally + rejectedReach) * 31 + noShape) * 31; tally = ((tally + tested) * 31 + blocked) * 31 + (stepDown ? 1 : 0); long now = Environment.TickCount64; lock (_reachGate) { var key = (moverId, cellId); if (_reachSeenQuery.TryGetValue(key, out var prev) && prev.Tally == tally && now - prev.Ms < 500) { return; } _reachSeenQuery[key] = (now, tally); } Console.WriteLine(string.Format( System.Globalization.CultureInfo.InvariantCulture, "[reach-q] mover=0x{0:X8} cell=0x{1:X8} stepDown={2} inCell={3} " + "exempt={4} reached={5} rejectedReach={6} noShape={7} tested={8} " + "blocked={9} pos=({10:F2},{11:F2},{12:F2}) t={13}", moverId, cellId, stepDown, inCell, exempt, reached, rejectedReach, noShape, tested, blocked, currPos.X, currPos.Y, currPos.Z, now)); } // ----------------------------------------------------------------------- // [support] / [geom] — #337 "what is holding this body up, and is the // collision geometry where the visual geometry is?" (2026-08-06 — // TEMPORARY, strip with the physics-probe family). // // WHY A NEW FAMILY RATHER THAN MORE [resolve]. // ACDREAM_PROBE_RESOLVE already prints, per resolve: in/target/out // position + cell, ok, groundedIn, a THREE-VALUE contact-plane token // (valid / lastKnown / none), the collision normal + responsible entity if // something was hit, and one walkable-polygon bool. That is enough to say // THAT the body stopped. It cannot say WHAT held it up, because it prints // no plane normal, no plane height, no terrain sample, and no attribution // for who wrote the plane. So on a "wedged on a rock" capture, (a) terrain // holding the body, (b) an object surface holding it somewhere other than // where the rock is drawn, and (c) an unobstructed transition that simply // fails to advance all produce the SAME [resolve] line. Two diagnoses this // campaign have already been refuted by measurement; a probe that cannot // separate the remaining three is not worth the launch. // // WHAT SEPARATES THEM. // [support] — per resolve, per body (players AND corpses/NPCs, which is // what makes the fall-through case observable at all). It samples the // OUTDOOR TERRAIN independently at the body's own out-XY and prints // the contact plane's own height at that same XY. Two independent // heights at one point: // cpZ@out == terrZ → terrain is the support, whatever set it. // cpZ@out >> terrZ → an object surface is the support. // cpValid=false → nothing is; the body is in free fall. // `cpSrc` names the code site that wrote the plane, so the classifier // and the provenance are cross-checkable rather than one inferring // the other. // [geom] — once per GfxObj that comes near the mover. Compares the // object's PHYSICS BSP vertex cloud against its VISUAL mesh AABB in // the same local frame. If the collision geometry is absent, empty, // displaced, or the wrong size, this line says so directly. That is // the working hypothesis's refutation test: `verdict=coincident` // kills "the collision isn't where the visual is" outright, and no // amount of movement-side evidence is then needed to rule it out. // // Neither line gates, orders, or mutates anything. Both are pure reads. // ----------------------------------------------------------------------- /// /// Initial state from ACDREAM_PROBE_SUPPORT=1. Enables the /// [support] and [geom] lines described above. Zero cost when /// off (one static bool read per resolve and per collision candidate). /// TEMPORARY — strip with the rest of the physics-probe family. /// public static bool ProbeSupportEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_SUPPORT") == "1"; /// Vertical agreement window, in metres, inside which the contact /// plane's height and the terrain's height at the same XY are called the /// same surface. public const float SupportSameSurfaceZ = 0.05f; /// Straight-up-component agreement window inside which the contact /// plane's tilt and the terrain triangle's tilt are called the same /// surface. 0.02 is roughly 1 degree near flat. public const float SupportSameSurfaceNormalZ = 0.02f; private static readonly object _supportGate = new(); private static readonly Dictionary _supportSeen = new(); private static readonly HashSet _geomSeen = new(); // Contact-plane provenance latch. Ten distinct sites call // CollisionInfo.SetContactPlane — terrain, object BSP (graph and flat), // cell BSP, three water paths, and a straight-up fallback — and the plane // they write is indistinguishable once stored, so [support]'s // classification would have no independent cross-check. // // This deliberately does NOT live on CollisionInfo. That object's stored // members are compared member-for-member by the flat/graph differential // referee and by the transition-scratch reset poison test; adding a // diagnostic field there makes both oracles report a difference that is // not a difference, and the only way to keep them green is to teach them // to skip a member — which is how a referee quietly stops refereeing. // [ThreadStatic] because a headless host ticks several sessions in // parallel and physics is synchronous within each. [ThreadStatic] private static string? _contactPlaneSourceMember; [ThreadStatic] private static int _contactPlaneSourceLine; /// /// Clear the provenance latch. Call once per resolve, before the sweep, so /// a resolve that establishes no plane reports none rather than the /// previous resolve's answer. No-op unless /// . /// public static void BeginContactPlaneAttribution() { if (!ProbeSupportEnabled) return; _contactPlaneSourceMember = null; _contactPlaneSourceLine = 0; } /// /// Record the site asserting a contact plane. Called by /// CollisionInfo.SetContactPlane with compiler-supplied literals; /// self-guarded, so it is a single flag test when the probe is off and /// allocates nothing when it is on. /// public static void RecordContactPlaneSource(string member, int line) { if (!ProbeSupportEnabled) return; _contactPlaneSourceMember = member; _contactPlaneSourceLine = line; } /// /// member:line of the last site to assert a contact plane since /// , or "none". /// public static string ContactPlaneSource => _contactPlaneSourceMember is { Length: > 0 } member ? string.Concat( member, ":", _contactPlaneSourceLine.ToString( System.Globalization.CultureInfo.InvariantCulture)) : "none"; /// /// Classify what is under the body. Pure function so the live probe and any /// offline reader agree on the vocabulary. /// /// none — no contact plane: the body is unsupported. /// terrain — the contact plane sits at the terrain's height /// AND shares its tilt. /// object — the contact plane sits clear of the terrain: some /// collision surface other than the ground is the support. /// coplanar-tilt-mismatch — same height, different tilt. /// Reported as its own answer rather than folded into either, because /// it is exactly what a collision mesh laid flat against the ground /// would look like and guessing between the two would be the third /// unverified diagnosis this campaign. /// no-terrain — no outdoor terrain under this XY (indoors, /// or the landblock is not resident): the comparison is unavailable /// and is said so rather than defaulted. /// /// public static string ClassifySupport( bool contactPlaneValid, bool terrainSampled, float contactPlaneZAtXY, float contactPlaneNormalZ, float terrainZ, float terrainNormalZ) { if (!contactPlaneValid) return "none"; if (!terrainSampled) return "no-terrain"; bool sameHeight = MathF.Abs(contactPlaneZAtXY - terrainZ) <= SupportSameSurfaceZ; bool sameTilt = MathF.Abs(contactPlaneNormalZ - terrainNormalZ) <= SupportSameSurfaceNormalZ; if (sameHeight && sameTilt) return "terrain"; if (sameHeight) return "coplanar-tilt-mismatch"; return "object"; } /// /// Evaluate a plane's height at a given XY. Returns /// when the plane is near-vertical, where a height is not defined — a wall /// is never a floor, and reporting a huge number for one would read as a /// displaced surface. /// public static bool TryPlaneZAt(in Plane plane, float x, float y, out float z) { float nz = plane.Normal.Z; if (MathF.Abs(nz) < 1e-4f) { z = float.NaN; return false; } z = -(plane.D + plane.Normal.X * x + plane.Normal.Y * y) / nz; return true; } /// /// One [support] line. Self-guards on /// . /// /// /// Volume control: per mover, the line re-emits IMMEDIATELY on any change /// in the state signature — the support classification, the ok / contact / /// walkable / stalled bits, a 0.1 m change in the body's height, or a /// 0.1 m change in its height above terrain — and otherwise at most once /// per 250 ms. A body falling through geometry therefore produces a line /// every 10 cm of descent, and a body standing still produces four lines a /// second. Nothing is aggregated away. /// /// /// /// is never omitted: /// feedback_probe_identity_attribution — a per-entity probe without /// an identity produced a wrong root cause once already, and this capture /// deliberately covers several bodies at once. /// /// public static void LogSupport( uint moverId, bool isPlayer, Vector3 inPos, uint inCell, Vector3 targetPos, Vector3 outPos, uint outCell, bool ok, bool groundedIn, bool contact, bool onWalkable, bool contactPlaneValid, Plane contactPlane, uint contactPlaneCellId, bool contactPlaneIsWater, string contactPlaneSource, bool lastKnownValid, Plane lastKnownPlane, bool terrainSampled, float terrainZ, Vector3 terrainNormal, uint terrainCellId, bool terrainIsWater, bool walkablePolygon, bool lastWalkablePolygon, float stepUpHeight, float stepDownHeight, Vector3 velocity) { if (!ProbeSupportEnabled) return; float cpZ = float.NaN; bool cpZDefined = contactPlaneValid && TryPlaneZAt(contactPlane, outPos.X, outPos.Y, out cpZ); if (!cpZDefined) cpZ = float.NaN; float cpNz = contactPlaneValid ? contactPlane.Normal.Z : float.NaN; float terrNz = terrainSampled ? terrainNormal.Z : float.NaN; string support = ClassifySupport( contactPlaneValid && cpZDefined, terrainSampled, cpZ, cpNz, terrainZ, terrNz); float commanded = Vector3.Distance(inPos, targetPos); float moved = Vector3.Distance(inPos, outPos); // "The body was told to move and did not." The 1 cm floor is the // resolver's own no-op scale, not a tuned threshold. bool stalled = commanded > 0.01f && moved <= 0.01f; float zAboveTerrain = terrainSampled ? outPos.Z - terrainZ : float.NaN; float cpAboveTerrain = terrainSampled && cpZDefined ? cpZ - terrainZ : float.NaN; long now = Environment.TickCount64; long signature = support.GetHashCode(); signature = signature * 31 + (ok ? 1 : 0); signature = signature * 31 + (groundedIn ? 1 : 0); signature = signature * 31 + (contact ? 1 : 0); signature = signature * 31 + (onWalkable ? 1 : 0); signature = signature * 31 + (contactPlaneValid ? 1 : 0); signature = signature * 31 + (stalled ? 1 : 0); signature = signature * 31 + (long)MathF.Floor(outPos.Z * 10f); signature = signature * 31 + (float.IsNaN(zAboveTerrain) ? 0 : (long)MathF.Floor(zAboveTerrain * 10f)); lock (_supportGate) { if (_supportSeen.TryGetValue(moverId, out var prev) && prev.Signature == signature && now - prev.Ms < 250) { return; } _supportSeen[moverId] = (now, signature); } var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[support] mover=0x{0:X8} isPlayer={1} t={2} support={3} " + "in=({4:F3},{5:F3},{6:F3}) inCell=0x{7:X8} " + "tgt=({8:F3},{9:F3},{10:F3}) out=({11:F3},{12:F3},{13:F3}) outCell=0x{14:X8} " + "ok={15} cmd={16:F3} moved={17:F3} stalled={18} " + "groundedIn={19} contact={20} onWalkable={21} " + "cpValid={22} cpSrc={23} cpCell=0x{24:X8} cpWater={25} " + "cpN=({26:F4},{27:F4},{28:F4}) cpNz={29:F4} floorZ={30:F4} cpWalkable={31} " + "cpZatOut={32:F3} " + "lkcpValid={33} lkcpNz={34:F4} " + "terrOk={35} terrZ={36:F3} terrNz={37:F4} terrWalkable={38} " + "terrCell=0x{39:X8} terrWater={40} " + "zAboveTerr={41:F3} cpAboveTerr={42:F3} " + "walkPoly={43} lastWalkPoly={44} stepUp={45:F3} stepDown={46:F3} " + "vel=({47:F3},{48:F3},{49:F3})", moverId, isPlayer, now, support, inPos.X, inPos.Y, inPos.Z, inCell, targetPos.X, targetPos.Y, targetPos.Z, outPos.X, outPos.Y, outPos.Z, outCell, ok, commanded, moved, stalled, groundedIn, contact, onWalkable, contactPlaneValid, contactPlaneSource, contactPlaneCellId, contactPlaneIsWater, contactPlaneValid ? contactPlane.Normal.X : float.NaN, contactPlaneValid ? contactPlane.Normal.Y : float.NaN, cpNz, cpNz, PhysicsGlobals.FloorZ, contactPlaneValid && cpNz >= PhysicsGlobals.FloorZ, cpZ, lastKnownValid, lastKnownValid ? lastKnownPlane.Normal.Z : float.NaN, terrainSampled, terrainZ, terrNz, terrainSampled && terrNz >= PhysicsGlobals.FloorZ, terrainCellId, terrainIsWater, zAboveTerrain, cpAboveTerrain, walkablePolygon, lastWalkablePolygon, stepUpHeight, stepDownHeight, velocity.X, velocity.Y, velocity.Z)); } /// /// Ask whether [geom] has already been emitted for this GfxObj. /// The line is a property of the ASSET, not of any moment, so once per /// process is the whole story and re-emitting it would bury the /// [support] stream. /// public static bool ShouldLogGeometry(uint gfxObjId) { if (!ProbeSupportEnabled) return false; lock (_supportGate) { return _geomSeen.Add(gfxObjId); } } /// /// One [geom] line: is this object's collision geometry where its /// visual geometry is? Caller MUST have claimed the id through /// . /// /// /// The verdict vocabulary, and what each one settles: /// /// no-physics-bsp / empty-physics-bsp — the object /// has no collision polygons at all. Everything a body does around it /// follows from that one fact and no movement-side theory is needed. /// no-visual-bounds — the comparison could not be made. Said /// out loud rather than silently treated as agreement. /// displaced — collision and visual are the same size but /// sit in different places. This is the working hypothesis, and this /// token is the only thing that confirms it. /// extent-mismatch — same place, different size. /// coincident — collision and visual agree. This REFUTES the /// working hypothesis for this object, and the cause is then on the /// movement side (terrain support, or the transition itself). /// /// /// public static void LogGeometry( uint gfxObjId, uint entityId, int bspNodeCount, int bspPolygonCount, int bspVertexCount, Vector3 rootSphereOrigin, float rootSphereRadius, bool physicsBoundsValid, Vector3 physicsMin, Vector3 physicsMax, bool visualBoundsValid, Vector3 visualMin, Vector3 visualMax, float visualRadius, Vector3 entityWorldPosition, float entityScale, float registeredRadius) { Vector3 physExtent = physicsBoundsValid ? physicsMax - physicsMin : Vector3.Zero; Vector3 visExtent = visualBoundsValid ? visualMax - visualMin : Vector3.Zero; Vector3 physCentre = physicsBoundsValid ? (physicsMin + physicsMax) * 0.5f : Vector3.Zero; Vector3 visCentre = visualBoundsValid ? (visualMin + visualMax) * 0.5f : Vector3.Zero; float centreDelta = physicsBoundsValid && visualBoundsValid ? Vector3.Distance(physCentre, visCentre) : float.NaN; // Tolerances are deliberately loose: this line answers "same place, // same size?" at the scale of a rock formation, not to the millimetre. // A physics hull is a coarse stand-in for the render mesh, so a // half-metre of centre drift or a 2x extent ratio is normal; what this // is looking for is the pathological case. float centreTolerance = visualBoundsValid ? MathF.Max(0.5f, visualRadius * 0.25f) : 0.5f; bool extentMismatch = false; if (physicsBoundsValid && visualBoundsValid) { for (int axis = 0; axis < 3; axis++) { float p = axis == 0 ? physExtent.X : axis == 1 ? physExtent.Y : physExtent.Z; float v = axis == 0 ? visExtent.X : axis == 1 ? visExtent.Y : visExtent.Z; // Flat axes (a floor plate) legitimately have ~0 extent in one // dimension on both sides; only compare where the visual has // real size. if (v < 0.1f) continue; float ratio = p / v; if (ratio is < 0.5f or > 2.0f) extentMismatch = true; } } string verdict = bspNodeCount == 0 ? "no-physics-bsp" : bspPolygonCount == 0 ? "empty-physics-bsp" : !visualBoundsValid ? "no-visual-bounds" : !physicsBoundsValid ? "no-physics-bounds" : centreDelta > centreTolerance ? "displaced" : extentMismatch ? "extent-mismatch" : "coincident"; var ci = System.Globalization.CultureInfo.InvariantCulture; Console.WriteLine(string.Format(ci, "[geom] gfx=0x{0:X8} verdict={1} entity=0x{2:X8} t={3} " + "bspNodes={4} bspPolys={5} bspVerts={6} " + "rootSphere=({7:F3},{8:F3},{9:F3}) rootR={10:F3} registeredR={11:F3} " + "physMin=({12:F3},{13:F3},{14:F3}) physMax=({15:F3},{16:F3},{17:F3}) " + "physExt=({18:F3},{19:F3},{20:F3}) " + "visMin=({21:F3},{22:F3},{23:F3}) visMax=({24:F3},{25:F3},{26:F3}) " + "visExt=({27:F3},{28:F3},{29:F3}) visR={30:F3} " + "centreDelta={31:F3} centreTol={32:F3} extentMismatch={33} " + "objPos=({34:F2},{35:F2},{36:F2}) scale={37:F3} " + "physWorldZ=[{38:F2},{39:F2}] visWorldZ=[{40:F2},{41:F2}]", gfxObjId, verdict, entityId, Environment.TickCount64, bspNodeCount, bspPolygonCount, bspVertexCount, rootSphereOrigin.X, rootSphereOrigin.Y, rootSphereOrigin.Z, rootSphereRadius, registeredRadius, physicsMin.X, physicsMin.Y, physicsMin.Z, physicsMax.X, physicsMax.Y, physicsMax.Z, physExtent.X, physExtent.Y, physExtent.Z, visualMin.X, visualMin.Y, visualMin.Z, visualMax.X, visualMax.Y, visualMax.Z, visExtent.X, visExtent.Y, visExtent.Z, visualRadius, centreDelta, centreTolerance, extentMismatch, entityWorldPosition.X, entityWorldPosition.Y, entityWorldPosition.Z, entityScale, // Rotation is NOT applied to these two world Z ranges: an // axis-aligned box is not rotation-invariant, so a rotated object // would report a box that is merely indicative. Both sides get the // SAME treatment, so their AGREEMENT (the thing being measured) // stays exact regardless. entityWorldPosition.Z + physicsMin.Z * entityScale, entityWorldPosition.Z + physicsMax.Z * entityScale, entityWorldPosition.Z + visualMin.Z * entityScale, entityWorldPosition.Z + visualMax.Z * entityScale)); } /// /// Resolve the collision-vs-visual comparison for one GfxObj straight from /// the SAME prepared assets the resolver itself queries, and emit its /// [geom] line. Going through the production accessors is the point: /// AP-156's lesson was that a probe reading geometry by a second route can /// report a shape the registry never emitted. Caller MUST have claimed the /// id through . /// /// /// The physics box is measured over the vertices of the polygons the BSP /// actually indexes, not over the whole polygon table — a table can carry /// rows no node references, and including those would report collision /// geometry that no query can ever reach. /// /// public static void LogGeometryFromAssets( uint gfxObjId, uint entityId, FlatGfxObjCollisionAsset? flat, GfxObjVisualBounds? visual, Vector3 entityWorldPosition, float entityScale, float registeredRadius) { int nodeCount = 0; int polygonCount = 0; int vertexCount = 0; Vector3 rootOrigin = Vector3.Zero; float rootRadius = 0f; bool physBoundsValid = false; var physMin = new Vector3(float.PositiveInfinity); var physMax = new Vector3(float.NegativeInfinity); FlatPhysicsBsp? bsp = flat?.PhysicsBsp; if (bsp is { RootIndex: >= 0 } && bsp.Nodes.Length > 0) { nodeCount = bsp.Nodes.Length; rootOrigin = bsp.Nodes[bsp.RootIndex].BoundingSphere.Origin; rootRadius = bsp.Nodes[bsp.RootIndex].BoundingSphere.Radius; FlatPolygonTable table = bsp.PolygonTable; foreach (FlatPhysicsBspNode node in bsp.Nodes) { FlatIndexRange range = node.PolygonIndexRange; for (int i = range.Start; i < range.EndExclusive; i++) { int polygonIndex = bsp.PolygonIndexStream[i]; if ((uint)polygonIndex >= (uint)table.Polygons.Length) continue; polygonCount++; FlatIndexRange vertices = table.Polygons[polygonIndex].VertexRange; for (int v = vertices.Start; v < vertices.EndExclusive; v++) { Vector3 p = table.Vertices[v]; vertexCount++; physMin = Vector3.Min(physMin, p); physMax = Vector3.Max(physMax, p); physBoundsValid = true; } } } } if (!physBoundsValid) { physMin = Vector3.Zero; physMax = Vector3.Zero; } LogGeometry( gfxObjId: gfxObjId, entityId: entityId, bspNodeCount: nodeCount, bspPolygonCount: polygonCount, bspVertexCount: vertexCount, rootSphereOrigin: rootOrigin, rootSphereRadius: rootRadius, physicsBoundsValid: physBoundsValid, physicsMin: physMin, physicsMax: physMax, visualBoundsValid: visual is not null, visualMin: visual?.Min ?? Vector3.Zero, visualMax: visual?.Max ?? Vector3.Zero, visualRadius: visual?.Radius ?? 0f, entityWorldPosition: entityWorldPosition, entityScale: entityScale, registeredRadius: registeredRadius); } /// /// Teleport-foundation timing probe (2026-06-22 — REMOVABLE diagnostic). /// Emits one [tp-probe] line per teleport-pipeline event with a /// cross-thread monotonic timestamp () /// so the offline reader can order AIM / ENQ / BUILD / APPLY / PLACED across /// the render thread and the streamer worker. Disambiguates the three /// candidate roots for "destination not resident fast": apply-THROTTLE /// (APPLY lands before PLACED), _datLock CONTENTION (BUILD waited= /// large), and a streaming-command GATE (ENQ never fires for the dest). /// Initial state from ACDREAM_PROBE_TELEPORT=1. Strip after capture. /// public static bool ProbeTeleportEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_TELEPORT") == "1"; /// /// One [tp-probe] line. Self-guards on , /// so callers need not pre-check (the cost when off is a single bool read). /// public static void LogTeleport(string point, uint id, string extra = "") { if (!ProbeTeleportEnabled) return; Console.WriteLine(System.FormattableString.Invariant( $"[tp-probe] {point,-6} id=0x{id:X8} t={Environment.TickCount64} {extra}")); } /// /// C4 route 3 D-T8 (2026-08-04 — TEMPORARY, strip with the rest of the /// physics-probe family once the connected gate is scored). One line per /// local-player portal-arrival attempt from /// RuntimeAcceptedPositionDriveController.ReconcileAndAcknowledgePortal /// — the single Runtime chokepoint both the graphical and headless hosts /// share, so this is dual-host parity evidence, not per-host guesswork. /// Initial state from ACDREAM_PROBE_LOCAL_TELEPORT=1. /// public static bool ProbeLocalTeleportEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_LOCAL_TELEPORT") == "1"; /// /// Which host process is running — set once at composition startup by /// each host's own entry point (SessionPlayerComposition for /// graphical, HeadlessSessionHost for headless). Runtime itself /// stays presentation-agnostic (Slice K); this is a diagnostics-only /// label so can report which /// process produced a given line without threading a host parameter /// through the drive controller's constructor. /// public static string LocalTeleportHostKind { get; set; } = "graphical"; /// /// One [local-tp] line: cause, host, placement status, portal /// generation/sequence, destination cell, resolved cell, and the three /// D-T8 booleans confirming the reconcile suffix actually ran /// ( = CommitCanonicalTeleportFrame /// executed, = the constraint leash is /// armed post-commit, = /// CancelAutoRun ran). Self-guards on /// . is /// always "portal" today — ACE's recall/admin teleports arrive as /// the identical TeleportAdvanced Position and are indistinguishable /// from a doorway portal at this layer; the parameter exists so a future /// wire-level cause signal has somewhere to land without a probe /// signature change. /// public static void LogLocalTeleportArrival( string cause, string placementStatus, long portalGeneration, ushort teleportSequence, uint destinationCell, uint resolvedCell, bool hookTailRan, bool leashArmed, bool autorunCancelled) { if (!ProbeLocalTeleportEnabled) return; string hookTailText = hookTailRan ? "ran" : "skipped"; string leashText = leashArmed ? "armed" : "unarmed"; string autorunText = autorunCancelled ? "cancelled" : "unchanged"; Console.WriteLine(System.FormattableString.Invariant( $"[local-tp] cause={cause} host={LocalTeleportHostKind} status={placementStatus} gen={portalGeneration} seq={teleportSequence} dest=0x{destinationCell:X8} resolved=0x{resolvedCell:X8} hookTail={hookTailText} leash={leashText} autorun={autorunText}")); } /// /// A6.P3 issue #98 step-walk investigation (2026-05-23). When true, /// emits one [step-walk] line at selected points in the transition /// sub-step loop and step-down probe. The line records requested vs /// adjusted offset, current/check sphere position, cell id, walk interp, /// contact planes, and walkable flags so a cellar-ramp capture can answer /// whether forward motion is being projected into rising Z or lost before /// the placement check. /// /// /// Initial state from ACDREAM_PROBE_STEP_WALK=1. One-shot /// diagnostic; no DebugPanel mirror until the root cause is identified. /// /// public static bool ProbeStepWalkEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_STEP_WALK") == "1"; /// /// A6.P3 issue #98 cell-dump probe (2026-05-23). When non-empty, dumps /// the geometry of any cached cell whose EnvCellId matches one of the /// listed ids to a JSON file under /// . One-shot per cell (a second cache /// of the same id is a no-op). /// /// /// Configured via ACDREAM_DUMP_CELLS as a comma-separated list of /// hex cell ids (with or without 0x prefix). The output path /// defaults to /// tests/AcDream.Core.Tests/Fixtures/issue98/<cellid>.json /// (relative to the worktree root). Override with /// ACDREAM_DUMP_CELLS_DIR=<dir>. /// /// /// /// The dump fuels the deterministic replay harness for issue #98 — the /// goal is to load cells 0xA9B40143 + 0xA9B40146 + 0xA9B40147 as JSON /// fixtures and drive the failing-frame sphere through the walkable /// query in a unit test, eliminating live-client iteration cost from the /// investigation loop. /// /// public static IReadOnlySet ProbeDumpCellIds { get; set; } = ParseHexIdList(Environment.GetEnvironmentVariable("ACDREAM_DUMP_CELLS")); public static string ProbeDumpCellsPath { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_DUMP_CELLS_DIR") ?? "tests/AcDream.Core.Tests/Fixtures/issue98"; public static bool ProbeDumpCellsEnabled => ProbeDumpCellIds.Count > 0; /// /// A6.P3 issue #98 (2026-05-23 evening v2) — GfxObj-equivalent of /// . When non-empty, dumps the polygon /// table + BSP root metadata of any cached GfxObj whose id matches /// one of the listed values, as a JSON fixture under /// . One-shot per id (a second /// cache of the same GfxObj is a no-op). /// /// /// Configured via ACDREAM_DUMP_GFXOBJS as a comma-separated /// list of hex GfxObj ids (with or without 0x prefix). Output /// defaults to tests/AcDream.Core.Tests/Fixtures/issue98 /// (relative to the worktree root) with one file per id named /// 0x{id:X8}.gfxobj.json so it doesn't collide with cell /// dumps in the same directory. Override directory via /// ACDREAM_DUMP_GFXOBJS_DIR=<dir>. /// /// /// /// The motivation: the existing [resolve-bldg] probe captures /// the GfxObj-level metadata (id, BSP root radius, entity origin) but /// emits hitPoly: n/a (BSP path — side-channel not written) /// because the BSPQuery wire site that would populate /// never landed. A polygon-level dump /// at cache time bypasses that gap entirely — one capture run yields /// the FULL polygon table, suitable for fixture-loading in /// CellarUpTrajectoryReplayTests's RegisterCottageGfxObj /// helper. /// /// public static IReadOnlySet ProbeDumpGfxObjIds { get; set; } = ParseHexIdList(Environment.GetEnvironmentVariable("ACDREAM_DUMP_GFXOBJS")); public static string ProbeDumpGfxObjsPath { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_DUMP_GFXOBJS_DIR") ?? "tests/AcDream.Core.Tests/Fixtures/issue98"; public static bool ProbeDumpGfxObjsEnabled => ProbeDumpGfxObjIds.Count > 0; /// /// Test-only reset: set every probe flag to false and clear any /// side-channel fields. Does NOT re-read environment variables — tests /// run in environments where the env vars are all absent, so false is /// the correct default. /// /// /// Call from test constructors and IDisposable.Dispose() to /// prevent one test class from leaving enabled probes that corrupt /// timing-sensitive tests in another class (the static-leak root cause /// documented in T0). /// /// /// /// This method is intentionally public so test projects can call /// it without reflection, but it must NEVER be called from production /// code paths. /// /// public static void ResetForTest() { ProbeResolveEnabled = false; ProbeCellEnabled = false; ProbeParkEnabled = false; ProbeBuildingEnabled = false; ProbeCellSetEnabled = false; ProbeStickyEnabled = false; ProbeAutoWalkEnabled = false; ProbeUseabilityFallbackEnabled= false; DumpSteepRoofEnabled = false; ProbeIndoorBspEnabled = false; ProbeCellCacheEnabled = false; ProbeContactPlaneEnabled = false; ProbeWalkMissEnabled = false; ProbePushBackEnabled = false; ProbePolyDumpEnabled = false; ProbePlacementFailEnabled = false; ProbeSweptEnabled = false; ProbeStepWalkEnabled = false; ProbeReachEnabled = false; lock (_reachGate) { _reachSeenObj.Clear(); _reachSeenQuery.Clear(); } ProbeSupportEnabled = false; _contactPlaneSourceMember = null; _contactPlaneSourceLine = 0; lock (_supportGate) { _supportSeen.Clear(); _geomSeen.Clear(); } ProbeTeleportEnabled = false; ProbeRemoteTeleportEnabled = false; ProbeRemoteLandingEnabled = false; ProbeRemoteSlideEnabled = false; ProbeRemoteSlideGuids = new System.Collections.Generic.HashSet(); _remoteSlideAttributionGuid = 0; _remoteSlideTickGate = null; // Side-channel fields LastBspHitPoly = null; LastPlacementFailPolyId = 0; LastPlacementFailPolyNormal = default; LastPlacementFailPolyD = 0f; LastPlacementFailSolidLeaf = false; // Dump-trigger sets ProbeDumpCellIds = new System.Collections.Generic.HashSet(); ProbeDumpGfxObjIds = new System.Collections.Generic.HashSet(); } private static IReadOnlySet ParseHexIdList(string? raw) { if (string.IsNullOrWhiteSpace(raw)) return new System.Collections.Generic.HashSet(); var ids = new System.Collections.Generic.HashSet(); foreach (var token in raw.Split(',', StringSplitOptions.RemoveEmptyEntries)) { var trimmed = token.Trim(); if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) trimmed = trimmed[2..]; if (uint.TryParse( trimmed, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out var id)) { ids.Add(id); } } return ids; } /// /// Side-channel populated by BSPQuery.SphereIntersectsSolidInternal /// at the leaf where it returns true. Either /// identifies the polygon that /// intersected the sphere, or /// is true to indicate the sphere center landed inside a BSP leaf /// marked solid (no specific polygon). The caller (Path 1) reads /// these immediately after the true return to emit the /// [place-fail] line, then clears them before the next test. /// /// /// Writes are gated on so the /// production path pays only one boolean check per leaf hit when the /// probe is off. /// /// public static ushort LastPlacementFailPolyId { get; set; } /// public static Vector3 LastPlacementFailPolyNormal { get; set; } /// public static float LastPlacementFailPolyD { get; set; } /// public static bool LastPlacementFailSolidLeaf { get; set; } /// /// Emit one [place-fail] line for a placement_insert rejection. /// tags the call site (e.g. /// "Path1.sphere0" for the foot sphere in Path 1, /// "Path1.sphere1" for the head sphere, /// "DoStepDown" for the wrapper). The polygon (or solid leaf) /// fields come from the side-channel populated during the recursive /// BSP descent. /// /// Caller MUST guard with if (!ProbePlacementFailEnabled) return;. /// public static void LogPlacementFail( string source, Vector3 sphereCenter, float radius, int sphereIdx, uint cellId, Vector3 worldOrigin, bool ethereal) { var ci = System.Globalization.CultureInfo.InvariantCulture; string polyDesc = LastPlacementFailSolidLeaf ? "solid_leaf=1" : LastPlacementFailPolyId != 0 ? string.Format(ci, "polyId=0x{0:X4} n=({1:F4},{2:F4},{3:F4}) d={4:F4}", LastPlacementFailPolyId, LastPlacementFailPolyNormal.X, LastPlacementFailPolyNormal.Y, LastPlacementFailPolyNormal.Z, LastPlacementFailPolyD) : "no_poly_info"; Console.WriteLine(string.Format(ci, "[place-fail] source={0} cell=0x{1:X8} sphere=({2:F4},{3:F4},{4:F4}) r={5:F4} " + "sphereIdx={6} worldOrigin=({7:F4},{8:F4},{9:F4}) ethereal={10} {11}", source, cellId, sphereCenter.X, sphereCenter.Y, sphereCenter.Z, radius, sphereIdx, worldOrigin.X, worldOrigin.Y, worldOrigin.Z, ethereal, polyDesc)); } /// /// A6.P1 emission helper for the AdjustSphereToPlane 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 /// CPolygon::adjust_sphere_to_plane. /// /// /// Caller MUST guard with if (!ProbePushBackEnabled) return; /// before computing the delta arguments — this method assumes the /// caller paid that price already. /// /// 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)); } /// /// A6.P1 emission helper for the FindCollisions 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 /// BSPTREE::find_collisions. /// /// /// Caller MUST guard with if (!ProbePushBackEnabled) return; /// before calling. /// /// 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)); } /// /// A6.P1 emission helper for the CheckOtherCells 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 /// CTransition::check_other_cells loop at decomp line /// 272717. Augments the existing A4 multi-cell BSP instrumentation /// with explicit per-iteration outcome telemetry. /// /// /// Caller MUST guard with if (!ProbePushBackEnabled) return; /// before calling. /// /// 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)); } /// /// Emit one [step-walk] line for issue #98's cellar-ramp /// investigation. Caller MUST guard with /// if (!ProbeStepWalkEnabled) return; before calling. /// public static void LogStepWalk( string site, int stepIndex, int stepCount, SpherePath sp, CollisionInfo ci, ObjectInfo oi, Vector3 requestedOffset, Vector3 adjustedOffset, TransitionState? state = null, string? detail = null) { var culture = System.Globalization.CultureInfo.InvariantCulture; var checkDelta = sp.CheckPos - sp.CurPos; string stateText = state.HasValue ? state.Value.ToString() : "n/a"; string stepText = stepIndex >= 0 && stepCount > 0 ? string.Format(culture, "{0}/{1}", stepIndex + 1, stepCount) : "-"; Console.WriteLine(string.Format(culture, "[step-walk] site={0} step={1} state={2} " + "cur=({3:F4},{4:F4},{5:F4}) check=({6:F4},{7:F4},{8:F4}) " + "delta=({9:F4},{10:F4},{11:F4}) cell=0x{12:X8}->0x{13:X8} " + "req=({14:F4},{15:F4},{16:F4}) adj=({17:F4},{18:F4},{19:F4}) " + "winterp={20:F4} stepUp={21} stepDown={22} insert={23} " + "oi=0x{24:X} contact={25} onWalkable={26} " + "cp={27} lkcp={28} hit={29} slide={30} walkPoly={31} lastWalkPoly={32}{33}", site, stepText, stateText, sp.CurPos.X, sp.CurPos.Y, sp.CurPos.Z, sp.CheckPos.X, sp.CheckPos.Y, sp.CheckPos.Z, checkDelta.X, checkDelta.Y, checkDelta.Z, sp.CurCellId, sp.CheckCellId, requestedOffset.X, requestedOffset.Y, requestedOffset.Z, adjustedOffset.X, adjustedOffset.Y, adjustedOffset.Z, sp.WalkInterp, sp.StepUp, sp.StepDown, sp.InsertType, (uint)oi.State, oi.Contact, oi.OnWalkable, FormatPlane(ci.ContactPlaneValid, ci.ContactPlane, ci.ContactPlaneCellId, ci.ContactPlaneIsWater), FormatPlane(ci.LastKnownContactPlaneValid, ci.LastKnownContactPlane, ci.LastKnownContactPlaneCellId, ci.LastKnownContactPlaneIsWater), FormatVector(ci.CollisionNormalValid, ci.CollisionNormal), FormatVector(ci.SlidingNormalValid, ci.SlidingNormal), sp.HasWalkablePolygon, sp.HasLastWalkablePolygon, string.IsNullOrEmpty(detail) ? string.Empty : " " + detail)); } /// /// A6.P3 issue #98 (2026-05-23) — focused probe INSIDE /// revealing which branch was /// taken and the per-call Z gain. Pair with [step-walk] /// site=after-adjust at the call site to triangulate where the /// projection ends up. Caller MUST guard with /// if (!ProbeStepWalkEnabled) return; before calling. /// public static void LogStepWalkAdjust( string branch, Vector3 input, Vector3 output, Plane? contactPlane, bool slidingValid, Vector3 slidingNormal, float collisionAngle, float walkInterp) { var culture = System.Globalization.CultureInfo.InvariantCulture; string cpDesc = contactPlane is { } cp ? string.Format(culture, "n=({0:F4},{1:F4},{2:F4}) d={3:F4}", cp.Normal.X, cp.Normal.Y, cp.Normal.Z, cp.D) : "n/a"; string slideDesc = slidingValid ? string.Format(culture, "({0:F4},{1:F4},{2:F4})", slidingNormal.X, slidingNormal.Y, slidingNormal.Z) : "n/a"; Console.WriteLine(string.Format(culture, "[step-walk-adjust] branch={0} input=({1:F4},{2:F4},{3:F4}) " + "output=({4:F4},{5:F4},{6:F4}) zGain={7:F4} " + "cp={8} slide={9} colAngle={10:F4} winterp={11:F4}", branch, input.X, input.Y, input.Z, output.X, output.Y, output.Z, output.Z - input.Z, cpDesc, slideDesc, collisionAngle, walkInterp)); } private static string FormatVector(bool valid, Vector3 value) { if (!valid) return "n/a"; return string.Format(System.Globalization.CultureInfo.InvariantCulture, "({0:F4},{1:F4},{2:F4})", value.X, value.Y, value.Z); } private static string FormatPlane(bool valid, Plane plane, uint cellId, bool isWater) { if (!valid) return "n/a"; float zAtOrigin = MathF.Abs(plane.Normal.Z) > PhysicsGlobals.EPSILON ? -plane.D / plane.Normal.Z : float.NaN; return string.Format(System.Globalization.CultureInfo.InvariantCulture, "cell=0x{0:X8},water={1},n=({2:F4},{3:F4},{4:F4}),d={5:F4},z0={6:F4}", cellId, isWater, plane.Normal.X, plane.Normal.Y, plane.Normal.Z, plane.D, zAtOrigin); } 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}")); } /// /// Walks the stack to identify the first frame outside CollisionInfo /// and PhysicsDiagnostics — that's the actual caller writing the /// ContactPlane field. Format: TypeName.MethodName:line when file /// info is available, else just TypeName.MethodName. Walked with /// fileNeeded=true only when the probe flag is on, so zero cost /// when off. /// 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 "?"; } private static int ParsePositiveInt(string? value) => int.TryParse( value, System.Globalization.NumberStyles.None, System.Globalization.CultureInfo.InvariantCulture, out int parsed) && parsed > 0 ? parsed : 0; }