diff --git a/docs/plans/2026-04-29-movement-collision-conformance.md b/docs/plans/2026-04-29-movement-collision-conformance.md index 8bfc2c1..cdabd45 100644 --- a/docs/plans/2026-04-29-movement-collision-conformance.md +++ b/docs/plans/2026-04-29-movement-collision-conformance.md @@ -92,6 +92,35 @@ Goal: make every bad movement outcome explainable. - Build real-DAT fixture capture for known walls, building ledges, rooftops, slopes, landblock seams, and dungeon entrances. +Current shipped slices: + +- 2026-04-30: cdb + TTD retail-observer toolchain (`tools/pdb-extract/`, + `tools/ttd-record.ps1`, `tools/ttd-query.ps1`) with PDB pairing checker + and ring-buffer trace replay. The "retail observer harness" line item. +- 2026-04 (pre-L.2 rename): `ACDREAM_DUMP_MOVE_TRUTH` paired + outbound/server-echo dumper in `GameWindow` covers outbound packet + fields + server echo + correction delta with cell-id mismatch. +- Pre-L.2: scenario-specific dumps `ACDREAM_DUMP_MOTION`, + `ACDREAM_DUMP_STEEP_ROOF`, `ACDREAM_DUMP_STEPUP`, + `ACDREAM_DUMP_EDGE_SLIDE` for the codepaths hit during prior bug chases. +- 2026-05-12 (slice 1): general-purpose probes via new + `AcDream.Core.Physics.PhysicsDiagnostics` static class. + `ACDREAM_PROBE_RESOLVE` emits one `[resolve]` line per + `PhysicsEngine.ResolveWithTransition` call (input/output pos+cell, + ok-vs-partial, grounded-in, contact-plane status, wall normal if hit, + walkable polygon valid, moving entity id). + `ACDREAM_PROBE_CELL` emits one `[cell-transit]` line per + `PlayerMovementController.CellId` change with old→new + position + + reason tag (`resolver`/`teleport`). Both flippable live via the + DebugPanel "Diagnostics" section — checkbox toggles take effect on + the next resolve, no relaunch required. + +Remaining L.2a work: contact-plane probe (general, not just steep-roof), +ShadowObjectRegistry hit log ("you collided with entity X"), water probe, +real-DAT fixture-capture pipeline, and folding the older sticky-at-startup +`ACDREAM_DUMP_*` flags into `PhysicsDiagnostics` for unified runtime +toggling. + ### L.2b - Movement Wire / Contact Authority Goal: stop sending movement packets that claim more certainty than the local diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index 1bc88b8..cb9b34b 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -288,12 +288,29 @@ public sealed class PlayerMovementController _motion.apply_current_movement(cancelMoveTo: false, allowJump: false); } + // L.2a slice 1 (2026-05-12): centralized CellId mutation so the + // [cell-transit] probe fires from a single chokepoint. Both the + // server-snap path (SetPosition) and the per-frame resolver path + // route through here. When PhysicsDiagnostics.ProbeCellEnabled is + // off this collapses to a single bool-compare + assignment — zero + // logging cost. + private void UpdateCellId(uint newCellId, string reason) + { + if (newCellId != CellId && PhysicsDiagnostics.ProbeCellEnabled) + { + var pos = _body.Position; + Console.WriteLine(System.FormattableString.Invariant( + $"[cell-transit] 0x{CellId:X8} -> 0x{newCellId:X8} pos=({pos.X:F3},{pos.Y:F3},{pos.Z:F3}) reason={reason}")); + } + CellId = newCellId; + } + public void SetPosition(Vector3 pos, uint cellId) { _body.Position = pos; _prevPhysicsPos = pos; _currPhysicsPos = pos; - CellId = cellId; + UpdateCellId(cellId, "teleport"); // Treat as grounded after a server-side position snap. _body.TransientState = TransientStateFlags.Contact | TransientStateFlags.OnWalkable; @@ -760,7 +777,7 @@ public sealed class PlayerMovementController _wasAirborneLastFrame = !_body.OnWalkable; - CellId = resolveResult.CellId; + UpdateCellId(resolveResult.CellId, "resolver"); // ── 6. Determine outbound motion commands ───────────────────────────── uint? outForwardCmd = null; diff --git a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs new file mode 100644 index 0000000..2440bb1 --- /dev/null +++ b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs @@ -0,0 +1,40 @@ +using System; + +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. +/// +/// +/// Slice 1 ships + +/// . 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 +{ + /// + /// 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"; +} diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index fe308ae..696a635 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -677,6 +677,28 @@ public sealed class PhysicsEngine $"deltaXY=({dx:F3},{dy:F3}) cp={cpInfo}"); } + // L.2a slice 1 (2026-05-12): general-purpose resolver probe. + // One line per call when PhysicsDiagnostics.ProbeResolveEnabled + // is set (env var ACDREAM_PROBE_RESOLVE=1 at startup, or the + // DebugPanel checkbox flipped at runtime). Captures every + // dimension L.2 cares about: input/output position, input/output + // cell, ok-vs-partial, grounded-in vs contact-out, contact-plane + // status, wall normal if hit, walkable polygon valid. Zero cost + // when off (one static-bool read). + if (PhysicsDiagnostics.ProbeResolveEnabled) + { + var probePost = sp.CheckPos; + string probeCp = ci.ContactPlaneValid + ? "valid" + : (ci.LastKnownContactPlaneValid ? "lastKnown" : "none"); + string probeHit = collisionNormalValid + ? System.FormattableString.Invariant( + $"yes n=({collisionNormal.X:F2},{collisionNormal.Y:F2},{collisionNormal.Z:F2})") + : "no"; + Console.WriteLine(System.FormattableString.Invariant( + $"[resolve] ent=0x{movingEntityId:X8} in=({currentPos.X:F3},{currentPos.Y:F3},{currentPos.Z:F3}) cell=0x{cellId:X8} tgt=({targetPos.X:F3},{targetPos.Y:F3},{targetPos.Z:F3}) out=({probePost.X:F3},{probePost.Y:F3},{probePost.Z:F3}) cell=0x{sp.CheckCellId:X8} ok={ok} groundedIn={isOnGround} cp={probeCp} hit={probeHit} walkable={sp.HasLastWalkablePolygon}")); + } + if (ok) { bool onGround = ci.ContactPlaneValid diff --git a/src/AcDream.UI.Abstractions/Panels/Debug/DebugPanel.cs b/src/AcDream.UI.Abstractions/Panels/Debug/DebugPanel.cs index dc55080..e990686 100644 --- a/src/AcDream.UI.Abstractions/Panels/Debug/DebugPanel.cs +++ b/src/AcDream.UI.Abstractions/Panels/Debug/DebugPanel.cs @@ -199,15 +199,21 @@ public sealed class DebugPanel : IPanel { if (!r.CollapsingHeader("Diagnostics", defaultOpen: true)) return; - bool dumpMotion = _vm.DumpMotion; - bool dumpVitals = _vm.DumpVitals; - bool dumpOpcodes = _vm.DumpOpcodes; - bool dumpSky = _vm.DumpSky; + bool dumpMotion = _vm.DumpMotion; + bool dumpVitals = _vm.DumpVitals; + bool dumpOpcodes = _vm.DumpOpcodes; + bool dumpSky = _vm.DumpSky; + bool probeResolve = _vm.ProbeResolve; + bool probeCell = _vm.ProbeCell; - if (r.Checkbox("Dump motion (ACDREAM_DUMP_MOTION)", ref dumpMotion)) _vm.DumpMotion = dumpMotion; - if (r.Checkbox("Dump vitals (ACDREAM_DUMP_VITALS)", ref dumpVitals)) _vm.DumpVitals = dumpVitals; - if (r.Checkbox("Dump opcodes (ACDREAM_DUMP_OPCODES)", ref dumpOpcodes)) _vm.DumpOpcodes = dumpOpcodes; - if (r.Checkbox("Dump sky (ACDREAM_DUMP_SKY)", ref dumpSky)) _vm.DumpSky = dumpSky; + if (r.Checkbox("Dump motion (ACDREAM_DUMP_MOTION)", ref dumpMotion)) _vm.DumpMotion = dumpMotion; + if (r.Checkbox("Dump vitals (ACDREAM_DUMP_VITALS)", ref dumpVitals)) _vm.DumpVitals = dumpVitals; + if (r.Checkbox("Dump opcodes (ACDREAM_DUMP_OPCODES)", ref dumpOpcodes)) _vm.DumpOpcodes = dumpOpcodes; + if (r.Checkbox("Dump sky (ACDREAM_DUMP_SKY)", ref dumpSky)) _vm.DumpSky = dumpSky; + // L.2a slice 1 (2026-05-12): unlike the four above, these + // forward to PhysicsDiagnostics so a toggle takes effect live. + if (r.Checkbox("Probe resolve (ACDREAM_PROBE_RESOLVE)", ref probeResolve)) _vm.ProbeResolve = probeResolve; + if (r.Checkbox("Probe cell-transit (ACDREAM_PROBE_CELL)",ref probeCell)) _vm.ProbeCell = probeCell; r.Spacing(); diff --git a/src/AcDream.UI.Abstractions/Panels/Debug/DebugVM.cs b/src/AcDream.UI.Abstractions/Panels/Debug/DebugVM.cs index 9e914af..693cc2d 100644 --- a/src/AcDream.UI.Abstractions/Panels/Debug/DebugVM.cs +++ b/src/AcDream.UI.Abstractions/Panels/Debug/DebugVM.cs @@ -1,5 +1,6 @@ using System.Numerics; using AcDream.Core.Combat; +using AcDream.Core.Physics; namespace AcDream.UI.Abstractions.Panels.Debug; @@ -234,6 +235,32 @@ public sealed class DebugVM /// Mirror of ACDREAM_DUMP_SKY. public bool DumpSky { get; set; } + // L.2a slice 1 (2026-05-12): unlike DumpMotion/Vitals/Opcodes/Sky + // above (which are display-only mirrors of sticky-at-startup env + // vars), these forward directly to the PhysicsDiagnostics statics, + // so checkbox toggles take effect on the next physics resolve. + /// + /// Runtime mirror of PhysicsDiagnostics.ProbeResolveEnabled + /// (env var ACDREAM_PROBE_RESOLVE). Toggling here flips the + /// resolver probe live — no relaunch required. + /// + public bool ProbeResolve + { + get => PhysicsDiagnostics.ProbeResolveEnabled; + set => PhysicsDiagnostics.ProbeResolveEnabled = value; + } + + /// + /// Runtime mirror of PhysicsDiagnostics.ProbeCellEnabled + /// (env var ACDREAM_PROBE_CELL). Toggling here flips the + /// cell-transit probe live. + /// + public bool ProbeCell + { + get => PhysicsDiagnostics.ProbeCellEnabled; + set => PhysicsDiagnostics.ProbeCellEnabled = value; + } + // ── Action hooks invoked by panel buttons ────────────────────────── ///