diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 7af44839..44f49b0b 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -24,9 +24,35 @@ What does NOT go here: - Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending. - Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed. -## #435 — Probe debt: 17 temporary probes outlived their closed investigations, 14 more name no owner +## #435 — PARTLY CLOSED: Probe debt: 17 temporary probes outlived their closed investigations, 14 more name no owner -**Status:** OPEN +**Status:** The 17 orphaned probes are DELETED (2026-08-24) — 3,493 lines +removed, flag count 161 → 144, temporary probes 64 → 47. Build clean; full +hermetic suite 15,321 passed / 0 failed (baseline 15,333 minus the 12 tests +whose only subject was a deleted probe). Four files went entirely: +`WalkMissDiagnostic.cs`, `CollisionMeshWireframe.cs` and two probe-only test +files. `LaunchOptionsDocumentationTests` did its job during the cleanup — +it refused the deletion until the doc's rows moved to Retired and the frozen +direct-read counts were lowered (`PhysicsEngine.cs` to zero, +`TransitionTypes.cs` 3 → 2). + +Notable: `TransitionTypes.SetContactPlane` shed its `CallerMemberName` / +`CallerLineNumber` parameters, which existed only for #337's `cpSrc=` +attribution and were explicitly marked "strip with the probe family". No +call site passed them, so no behavior changed. F2's collision overlay +survives and reverts to the proxy-cylinder form, as intended when +`ACDREAM_WIRE_MESH` went. + +**STILL OPEN — the 14 unattributed probes.** They name no owning issue, so +nothing records when they are safe to remove. Deleting them on a guess is +how a future investigation loses apparatus it needed. The right next step is +attribution, not deletion: for each, find the commit that introduced it +(`git log -S ACDREAM_PROBE_X`), record the issue in its +`docs/launch-options.md` row, and only then decide. Deliberately deferred. + +**Original report follows.** + +**Status (original):** OPEN **Severity:** LOW (no runtime defect; hot-path clutter and measurement noise) **Filed:** 2026-08-24 (measured during the launch-options audit) **Component:** diagnostics ownership diff --git a/docs/launch-options.md b/docs/launch-options.md index de8c528b..d732c5fe 100644 --- a/docs/launch-options.md +++ b/docs/launch-options.md @@ -260,26 +260,24 @@ Each row names the issue that owns it. **A temporary probe is deleted in the same commit as its investigation's fix** — if you find one here whose issue is closed, the strip was missed; delete both. -> **Probe debt, measured 2026-08-24:** 64 temporary probes exist. They cite -> 21 distinct issues, and **14 of those are already closed** — 17 rows below -> are apparatus whose investigation ended without the strip. A further 14 -> rows name no owning issue at all, which is worse: nobody can tell when -> they are safe to remove. Tracked as [#435](ISSUES.md); do not add to the -> pile. Every probe here still costs a branch on its hot path even when -> unset, and a handful re-read the environment per frame rather than -> caching (see their side-effects column). +> **Probe debt, measured 2026-08-24:** 64 temporary probes existed, citing 21 +> distinct issues with 14 already closed. [#435](ISSUES.md) stripped the 17 +> rows whose investigation had ended without the strip — see the Retired +> section below for their removal record — leaving 47. A further 14 rows +> (unchanged by this pass) name no owning issue at all, which is worse: +> nobody can tell when they are safe to remove. Every probe here still costs +> a branch on its hot path even when unset, and a handful re-read the +> environment per frame rather than caching (see their side-effects column). | Flag | Owning investigation | Value | What it does | Side effects | Read by | |---|---|---|---|---|---| | `ACDREAM_A8_DUMP_PV` | (unattributed) | `=1` | Dumps local→NDC→clipped portal geometry (first 2 `Build` calls per distinct camera cell) | print-only (`Console.WriteLine`) | `PortalVisibilityBuilder.cs:270-271` (static field, not a diagnostics-owner class) | -| `ACDREAM_AIRBORNE_DIAG` | #42 | `=1` | prints `[SWEEP]`/`[SWEEP-OBJ]` lines tracing airborne-sweep XY drift, only when `!isOnGround` | print-only; re-reads the env var (`Environment.GetEnvironmentVariable`) on every airborne resolve/candidate instead of caching — minor per-call overhead when the flag is unset too | raw reads in `PhysicsEngine.cs:2300` + `TransitionTypes.cs:3905` (issue #42) | | `ACDREAM_CLIP_DEBUG` | #176 | `=1` | forces the EnvCell SHELL pass to map every instance to clip slot 0 (no-clip) instead of its cell's portal-slice region | ALTERS RENDERED OUTPUT: shells draw whole/unclipped instead of trimmed — a visual isolation mode, not a log-only probe; no DebugPanel mirror | `RenderingDiagnostics.ClipDebugNoShellTrim` | | `ACDREAM_DUMP_APPEARANCE` | #5 | `="1"` | Logs every `0xF625` ObjDescEvent + `0xF7DB` UpdateObject with body length, target guid, hex preview — used to debug remote-player appearance asymmetry | print-only (`Console.WriteLine`) | `WorldSession` static field `DumpAppearanceEnabled` (`WorldSession.cs:792-793`), raw scattered read, issue #5 diagnostic | | `ACDREAM_DUMP_CELLS` | #98 | `=` | one-shot JSON dump of any cached EnvCell whose id matches the list, to `ProbeDumpCellsPath` (issue #98 fixture capture) | file I/O once per matching cell id (no-op on repeat); fixture-generation tool, not a perf-neutral no-op when ids are listed | `PhysicsDiagnostics.ProbeDumpCellIds` (`ParseHexIdList`) | | `ACDREAM_DUMP_CELLS_DIR` | (unattributed) | `=` | overrides the output directory for `ACDREAM_DUMP_CELLS` | print/file-path only; no effect unless `ACDREAM_DUMP_CELLS` is also set | `PhysicsDiagnostics.ProbeDumpCellsPath` | | `ACDREAM_DUMP_CLOTHING` | (unattributed) | `=1` | Print-only: dumps clothing/part-swap diagnostics for a spawned entity when its setup has ≥10 mesh parts (humanoids). Gated additionally on part count even when the flag is on. | `print-only` | `RuntimeOptions.DumpClothing` → `DatLiveEntityProjectionMaterializer.cs:251-258,1190` | | `ACDREAM_DUMP_EDGE_SLIDE` | (unattributed) | `=1` | gates five `edge-slide:` trace lines (stepdown-failed, stepdown-branch-enter, phase2, branch, cliffslide) inside the L.4-diag edge-slide/cliff-slide code path | print-only; property re-reads `Environment.GetEnvironmentVariable` on EVERY call (not cached in a field) — repeated env lookups during edge-slide resolution when active; raw read outside any diagnostics-owner class (rule-5 candidate) | `Transition.DumpEdgeSlideEnabled` (private expression-bodied property in `TransitionTypes.cs`, raw read) | -| `ACDREAM_DUMP_ENTITY` | #119 | comma-separated hex ids, optional `0x` prefix, malformed segments ignored | per-entity HYDRATE/DRAW/WALK-REJECT trace for a watchlist of Setup/GfxObj source ids across `LandblockBuildFactory`, `WbDrawDispatcher` | print-only; every call site fast-exits on `Count==0`. The same id set is ALSO reused (undocumented in its own XML doc) as the watchlist for the `ACDREAM_PROBE_OUTSTAGE` `[outstage-own]` per-entity verdict probe — see Notes | `RenderingDiagnostics.DumpEntitySourceIds` | | `ACDREAM_DUMP_GFXOBJS` | #98 | `=` | one-shot JSON dump of any cached GfxObj's polygon table + BSP root metadata matching the list, to `ProbeDumpGfxObjsPath` (issue #98 fixture capture) | file I/O once per matching id (no-op on repeat) | `PhysicsDiagnostics.ProbeDumpGfxObjIds` (`ParseHexIdList`) | | `ACDREAM_DUMP_GFXOBJS_DIR` | (unattributed) | `=` | overrides the output directory for `ACDREAM_DUMP_GFXOBJS` | print/file-path only; no effect unless `ACDREAM_DUMP_GFXOBJS` is also set | `PhysicsDiagnostics.ProbeDumpGfxObjsPath` | | `ACDREAM_DUMP_LIVE_SPAWNS` | (unattributed) | `=1` | Print-only: logs every live `CreateObject` spawn as it's processed, plus DROP lines when a setup dat id is missing. | `print-only` | `RuntimeOptions.DumpLiveSpawns` → `DatLiveEntityProjectionMaterializer.cs:161-226`, `SessionPlayerComposition.cs:566,712` | @@ -294,7 +292,6 @@ issue is closed, the strip was missed; delete both. | `ACDREAM_DUMP_VITALS` | (unattributed) | `="1"` | Logs every `PrivateUpdateVital(Current)` parse, every parsed `PlayerDescription` (vector flags/attr/spell counts), and `PlayerDescriptionParser` trailer/mid-walk `FormatException` failures with position | print-only at every site. `PlayerDescriptionParser.cs:458/473` re-read the env var raw inside `catch` blocks on every parse failure (rare, but scattered/uncached). | Read independently (not shared) at 4 sites: `WorldSession.cs:790-791` (`DumpVitalsEnabled`), `GameEventWiring.cs:1041` (local `dumpPd` at PlayerDescription registration), `PlayerDescriptionParser.cs:458` and `:473` (per-catch-block raw reads). Also mirrored (display-only, non-functional) via `DebugPanel.cs:240`/`DebugVM.cs:225`. | | `ACDREAM_HIDE_PART` | (unattributed) | `=` | Hides one mesh part by index on entities with ≥10 parts (humanoids) — a debugging aid for equipment/clothing part-visibility issues. | Real (visible) behavior change, not print-only, but scoped to a single diagnostic index and off by default. | `RuntimeOptions.HidePartIndex` → `LivePresentationComposition.cs:608` → `LiveEntityAnimationPresenter.cs:21,38,243` | | `ACDREAM_LIGHT_DEBUG` | #176 | `=` (`int.TryParse`; unset/invalid → 0) | shader isolation mode uploaded as `uLightDebug` by `EnvCellRenderer` + `WbDrawDispatcher`: 0=off, 1=ambient-only vertex lighting, 2=kill dynamic point lights, 3=raw vLit visualization (texture ignored) | ALTERS RENDERED OUTPUT directly every draw pass (changes fragment-shader lighting/texturing) — not a log probe; no DebugPanel mirror | `RenderingDiagnostics.LightDebugMode` | -| `ACDREAM_PROBE_AUTOWALK` | issue #63 | `=1` | gates `[autowalk-out]`/`[autowalk-mt]`/`[autowalk-up]` lines in `LiveEntityNetworkUpdateController` tracing local-player server-initiated auto-walk (`SendUse`/`SendPickUp`, inbound `UpdateMotion`, inbound `UpdatePosition`) | print-only; filtered to local player only, low volume | `PhysicsDiagnostics.ProbeAutoWalkEnabled` | | `ACDREAM_PROBE_BUILDING` | l.2d slice 1 | `=1` | gates the multi-line `[resolve-bldg]` BSP-shadow-hit trace in `TransitionTypes.FindObjCollisions`, one-time `[entity-source]` registration logs in `GameWindow`, `[door-cycle]` UM dispatch trail, and a one-shot `[setstate-hex]` wire dump of the first `SetState` (0xF74B) packet in `WorldSession` | print-only; also un-gates the `PhysicsDiagnostics.LastBspHitPoly` diagnostic side-channel (a static field write in `BSPQuery`/`FlatBspQuery`, read back by the `[resolve-bldg]` line) — no gameplay effect, but an extra static-field write per BSP hit while on; heavy output (one multi-line entry per BSP hit per physics tick) | `PhysicsDiagnostics.ProbeBuildingEnabled` | | `ACDREAM_PROBE_CELL` | (unattributed) | `=1` | gates one `[cell-transit]` line per `PlayerMovementController.CellId` change (old→new cell, position, reason tag) | print-only; low volume (only on actual cell crossings) | `PhysicsDiagnostics.ProbeCellEnabled` | | `ACDREAM_PROBE_CELLSET` | a6.p5 | `=1` | gates `PhysicsDiagnostics.LogCellSetBuild`, one `[cellset-build]` line per `BuildCellSetAndPickContaining` call (seed cell, sphere XY, candidate list) from `CellTransit.cs:1468` | print-only; builds a `StringBuilder` of the candidate id list only when the flag is on | `PhysicsDiagnostics.ProbeCellSetEnabled` | @@ -308,34 +305,19 @@ issue is closed, the strip was missed; delete both. | `ACDREAM_PROBE_INDOOR_BSP` | indoor walking phase 1 / cellar-lip wedge | `=1` | gates `[indoor-bsp]` (per `BSPQuery.FindCollisions` indoor call), `[neg-poly]` (near-miss polygon detail in `BSPQuery`), and `[stepdown-decide]` (step-down accept/reject inputs in `TransitionTypes`) trace lines | print-only; also un-gates the `LastBspHitPoly` diagnostic side-channel write (same as `ACDREAM_PROBE_BUILDING`) | `PhysicsDiagnostics.ProbeIndoorBspEnabled` | | `ACDREAM_PROBE_INDOOR_LIGHT` | #176/#177 discriminator, a7.l1 | `=1` | rate-limited (1 Hz) `[indoor-light]` line from `LightManager.BuildPointLightSnapshot`: point-light pool set composition (pool/cellLess/registered/capped/byCell histogram) | print-only, explicitly "inert unless set" per the call-site comment (LightManager.cs:368-370); no DebugPanel mirror | `RenderingDiagnostics.ProbeIndoorLightEnabled` | | `ACDREAM_PROBE_JUMP` | campaign ch round 2 | `=1` | gates the `[jump]` line in `PlayerMovementController.ReportJumpRefusal`, printed UNCONDITIONALLY (even when `OnInterfaceText` is null) to distinguish "branch never fired" from "branch fired, callback dropped it" | print-only; `Headless/Policies/HeadlessBotPolicy.cs`'s `JumpProbeHeadlessBotPolicy` doc comment references this flag as a companion but does not itself read it — it is a headless bot behavior meant to be run alongside `ACDREAM_PROBE_JUMP=1`, not a second consumer | `PhysicsDiagnostics.ProbeJumpEnabled` | -| `ACDREAM_PROBE_LIGHT` | #133 a7 | `=1` | rate-limited (1 Hz) `[light]` line + up to 3 `[light-detail]` lines: scene ambient/sun/registered/active light counts and nearest active point/spot light detail | print-only ("Output-only, inert when off" per doc) | `RenderingDiagnostics.ProbeLightEnabled` | | `ACDREAM_PROBE_LOCAL_TELEPORT` | c4 route 3 d-t8 | `=1` | gates one `[local-tp]` line per local-player portal-arrival attempt (committed AND refused) from `LocalPlayerTeleportController` and `RuntimeAcceptedPositionDriveController.LogPortalArrivalAttempt` — the single Runtime chokepoint both graphical and headless hosts share | print-only; dual-host parity evidence (same line shape from both hosts) | `PhysicsDiagnostics.ProbeLocalTeleportEnabled` | -| `ACDREAM_PROBE_OUTSTAGE` | #131 | `=1` | print-on-change `[outstage]` line (outside-stage routing + per-slice cone verdicts) from `RetailPViewRenderer`; plus, when `ACDREAM_DUMP_ENTITY` also names watched ids, `[outstage-own]` per-entity PASS/CULL lines | print-only | `RenderingDiagnostics.ProbeOutStageEnabled` | | `ACDREAM_PROBE_PARK` | issue #309 | `=1` | gates `[park]`/`[park-restore]` lines when a `RuntimeSetPositionState` placement parks or a cancelled park's withdrawal is rolled back | print-only, low volume (parks are rare); in a MULTI-session headless host, `HeadlessStaticStateAudit.ValidateProcessIsolation` THROWS `HeadlessConfigurationException` at startup if this (or any other process-global `Probe*`/`Dump*` boolean, `CollisionShadowSampleEvery`, or `PhysicsResolveCapture`) is enabled — refusal is waived only when `sessionCount==1` (logs loudly and proceeds instead) | `PhysicsDiagnostics.ProbeParkEnabled` | -| `ACDREAM_PROBE_PHANTOM` | #113, "throwaway apparatus — strip when the phantom closes" | `=1` | print-on-change `[phantom-shell]` / `[phantom-objs]` lines identifying which draw mechanism (shell pass vs. entity list) draws geometry unclipped/un-viewcone'd per cell | print-only | `RenderingDiagnostics.ProbePhantomEnabled` | | `ACDREAM_PROBE_PLACEMENT_FAIL` | issue #98 | `=1` | gates one `[place-fail]` line per Path-1 (Placement/Ethereal) `Collided` return in `BSPQuery.FindCollisions`, plus one per `Transition.DoStepDown` placement-insert rejection | print-only; low volume (fires only on actual rejection) | `PhysicsDiagnostics.ProbePlacementFailEnabled` | | `ACDREAM_PROBE_POLY_DUMP` | a6.p3 slice 4, issue #98 | `=1` | gates one `[poly-dump]` line (full polygon geometry: cell, poly index, sides, plane, all vertices) per `AdjustSphereToPlane` push-back call | print-only; HEAVY output (one full-geometry dump per push-back call) — doc explicitly says "use briefly, then turn off" | `PhysicsDiagnostics.ProbePolyDumpEnabled` | | `ACDREAM_PROBE_PORTAL_CHURN` | "throwaway apparatus — strip once the bound ships" | `=1` | one `[portal-churn]` summary per `PortalVisibilityBuilder.Build` call: per-cell pop/re-pop counts, re-enqueue totals, reciprocal-clip pre→post region growth | print-only | `RenderingDiagnostics.ProbePortalChurnEnabled` | | `ACDREAM_PROBE_PUSH_BACK` | phase a6.p1 | `=1` | gates `[push-back]` (`BSPQuery.AdjustSphereToPlane`), `[push-back-disp]` (`BSPQuery.FindCollisions` 6-path dispatcher), `[push-back-cell]` (`Transition.CheckOtherCells` multi-cell BSP) lines | print-only; the `DebugVM.cs:380` "runtime mirror" is dead code — `DebugVM`/`DebugPanel` (`AcDream.UI.Abstractions/Panels/Debug/`) are never instantiated anywhere in `src/` (the ImGui frontend they required was removed at Campaign V slice V11); only the startup env var takes effect | `PhysicsDiagnostics.ProbePushBackEnabled` | | `ACDREAM_PROBE_PVINPUT` | "throwaway apparatus — strip once the jitter source is pinned" | `=1` | one `[pv-input]` line/frame with 6-dp-precision `PortalVisibilityBuilder.Build` inputs (camera eye, player position, VP elements) + resulting flood-cell count; deliberately runs WITHOUT the heavier `[flap]` probe so the log stays diffable | print-only | `RenderingDiagnostics.ProbePvInputEnabled` | -| `ACDREAM_PROBE_REACH` | #334, temporary — strip with the probe family | `=1` | gates `[reach-q]` (per-cell candidate-disposition query summary, emitted even on zero-entry cells) and `[reach-obj]` (per-candidate disposition: exempt/no-shape/bsp-only-skip/tested) lines in `Transition.FindObjCollisionsInCell` | print-only; runs on a HOT path (per cell per transitional insert); de-duplicated via two `Dictionary` caches with a `lock`-protected gate (`_reachGate`) — a real per-call dictionary lookup + occasional lock contention while on, bounded emission (≤2/sec per cell, ≤1/sec per candidate) | `PhysicsDiagnostics.ProbeReachEnabled` | -| `ACDREAM_PROBE_REMOTE_LANDING` | bug a / issue #32, temporary | `=1` | gates `[remote-landing]`/`[remote-landing-gate]`/`[remote-landing-after]` lines around remote ground-contact edges in `LiveEntityNetworkUpdateController` and `RuntimeRemotePhysicsUpdater`; `MotionTableDispatchSink.ApplyMotion` unconditionally forwards its result to `PhysicsDiagnostics.RecordRemoteLandingDispatch` (self-guarded internally, no behavior change) | print-only; uses `[ThreadStatic]` capture latches (`_remoteLandingApplyCalls` etc.) so a headless host ticking several sessions in parallel doesn't cross-contaminate | `PhysicsDiagnostics.ProbeRemoteLandingEnabled` | | `ACDREAM_PROBE_REMOTE_SLIDE` | bug b, temporary — strip once two-client roof capture lands | `=1` OR `=` | gates `[remote-slide-up]`/`[remote-slide-vec]`/`[remote-slide-snap]`/`[remote-slide-enq]` lines across `LiveEntityNetworkUpdateController`, `InterpolationManager`, `RuntimeRemotePhysicsUpdater`, `RuntimeRemoteSteadyStatePosition` tracing two candidate remote-slide "blip" producers | print-only; `BeginRemoteSlideAttribution`/GUID-stamping calls are UNCONDITIONAL at several call sites (self-guard is internal), so a `[ThreadStatic]` field write happens on every remote tick regardless of the flag (cheap, non-allocating); a GUID allow-list narrows output to specific entities for a readable two-client capture | `PhysicsDiagnostics.ProbeRemoteSlideEnabled` + `ProbeRemoteSlideGuids` (raw string parsed via `ParseHexIdList` unless it's the literal `"1"`) | | `ACDREAM_PROBE_REMOTE_TELEPORT` | c4 route 4b-3, temporary | `=1` | gates one `[remote-teleport]` line per routed remote teleport arm in `LiveEntityNetworkUpdateController.ApplyRemoteContactRouting` | print-only; a 2026-08-04 fix moved the enabled-check to the CALL SITE because the probe's internal self-guard did not prevent `teleportStatus.ToString()` from being evaluated/allocated on every teleport regardless of flag state — now properly guarded | `PhysicsDiagnostics.ProbeRemoteTeleportEnabled` | | `ACDREAM_PROBE_SEAMDRAW` | #176, "throwaway apparatus" | `"1"`/`"true"`/blank → default #176 Facility Hub cell set (7 fixed hex ids); otherwise comma-separated hex cell-id list | change-deduped + 2 s-heartbeat `[seam-cell]`/`[seam-snap]`/`[seam-ent]`/`[seam-mask]` lines from `EnvCellRenderer.Render` and `WbDrawDispatcher` describing per-instance transforms and resolved light-set identities at target cells | print-only | `RenderingDiagnostics.ProbeSeamDrawEnabled` / `SeamDrawTargetCells` | -| `ACDREAM_PROBE_SHELL` | #78, "throwaway apparatus — strip once the indoor-enclosure render is fixed" | `=1` | one `[shell]` line per opaque-pass `EnvCellRenderer.Render` call: per filtered cell — snapshot presence, gfxObj/batch/index/translucent/zero-bindless-handle counts | print-only; allocates a `StringBuilder` and loops every visible cell on every opaque pass while enabled | `RenderingDiagnostics.ProbeShellEnabled` | -| `ACDREAM_PROBE_STEP_HEIGHTS` | issue #338 | `=1` | gates edge-triggered `[step-h]` lines at `prepare`/`publish`/`resolve` sites tracing step-up/step-down height provenance | print-only; `AnnounceStepHeightProbeOnce` prints TWO self-report lines EXACTLY ONCE PER PROCESS regardless of the flag's value (reports the flag's own state + the raw env var text + the running assembly's file path) — this self-report line is NOT gated by the flag itself, only rate-limited to once | `PhysicsDiagnostics.ProbeStepHeightsEnabled` | | `ACDREAM_PROBE_STEP_WALK` | a6.p3 issue #98 | `=1` | gates `[step-walk]` lines at select points in the transition sub-step loop and step-down probe (requested vs adjusted offset, sphere positions, contact planes, walkable flags) | print-only; no DebugPanel mirror | `PhysicsDiagnostics.ProbeStepWalkEnabled` | -| `ACDREAM_PROBE_STICKY` | r5-v3 issue #171 | `=1` | gates `[sticky]` lifecycle lines (STICK/UNSTICK/LEASE-EXPIRE/TARGET-status teardown) and per-armed-tick steer lines in `AcDream.Core.Physics.Motion.StickyManager`, plus `[sticky-snap-skip]` in `LiveEntityNetworkUpdateController` when a server hard-snap is suppressed for a stuck entity | print-only; heavy while a pack is stuck (~60 Hz × stuck count) | `PhysicsDiagnostics.ProbeStickyEnabled` | -| `ACDREAM_PROBE_SUPPORT` | issue #337, temporary | `=1` | gates `[support]` (per resolve, per body INCLUDING corpses/NPCs — independent terrain sample at the body's out-XY compared against the contact plane's height/provenance) and `[geom]` (once per nearby GfxObj — physics-BSP vertex cloud vs visual mesh AABB coincidence verdict) lines in `PhysicsEngine` | print-only; `[support]` performs an INDEPENDENT terrain height sample every time it fires (real extra computation beyond the resolve itself, throttled to 4 Hz per body plus every 10 cm of vertical movement); pure reads only, never mutates production collision state | `PhysicsDiagnostics.ProbeSupportEnabled` | | `ACDREAM_PROBE_SWEPT` | phase w stage 0 | `=1` | gates one `[cell-swept]` line per `ResolveWithTransition` call comparing the transition's swept cell vs the legacy static `ResolveCellId` path | print-only | `PhysicsDiagnostics.ProbeSweptEnabled` | | `ACDREAM_PROBE_TELEPORT` | 2026-06-22, "removable diagnostic" | `=1` | gates `[tp-probe]` lines (`LogTeleport`) at AIM/ENQ/BUILD/APPLY/PLACED teleport-pipeline events across `LocalPlayerTeleportController` and `RuntimeAcceptedPositionDriveController`, with cross-thread monotonic timestamps | print-only | `PhysicsDiagnostics.ProbeTeleportEnabled` | -| `ACDREAM_PROBE_TEXFLUSH` | #105 | `=1` | one `[tex-flush]` line whenever `WbMeshAdapter.Tick`'s staged-texture-update picture changes (pending layer updates before/after the per-frame mipmap flush) | print-only | `RenderingDiagnostics.ProbeTexFlushEnabled` | -| `ACDREAM_PROBE_VIEWER` | #119-residual | `=1` | one `[viewer]` line per CHANGE of (root cell, flood size, OutsideView poly count, player cell), with mm-precision projection eye — capture half of the tower-ascent capture→replay loop (`TowerAscentReplayTests`) | print-only | `RenderingDiagnostics.ProbeViewerEnabled` | -| `ACDREAM_PROBE_WALK_MISS` | issues #83, spike-only | `=1` | gates `[walk-miss]` (per `Transition.TryFindIndoorWalkablePlane` MISS) and `[floor-polys]` (per indoor cell cached, enumerating walkable-eligible polygons) lines | print-only; no DebugPanel mirror | `PhysicsDiagnostics.ProbeWalkMissEnabled` | -| `ACDREAM_WIRE_MESH` | #337, explicitly "temporary" | `=1` | when the separate F2 collision-wireframe overlay is already active, replaces its cheap broadphase-proxy-cylinder drawing with the object's REAL physics-BSP polygon edges (cyan) + visual mesh AABB (magenta) + terrain triangle under the player (yellow), resolved live every frame | ALTERS RENDERED OUTPUT (debug overlay geometry): adds real per-frame physics-BSP polygon extraction + line-drawing cost while F2 is on; only takes effect when the separate F2 toggle (`_state.CollisionWireframesVisible`) is also enabled; also emits a throttled print-on-change stats line | `RenderingDiagnostics.CollisionMeshWireframeEnabled` | -| `ACDREAM_WIRE_RADIUS` | companion knob to #337/`acdream_wire_mesh` | `=` (`float.TryParse`, invariant culture; falls back to 30 if unparsable or ≤0) | sets the radius around the player within which `CollisionMeshWireframeEnabled` resolves polygon geometry | larger radius = more physics-BSP polygon extraction/line-drawing cost per frame; only matters while `ACDREAM_WIRE_MESH=1` | `RenderingDiagnostics.CollisionMeshWireframeRadius` | - ## Deprecated | Flag | Value | What it does | Side effects | Default | Read by | @@ -360,3 +342,20 @@ from the "must still exist" check. | `ACDREAM_RENDER_BACKEND` | Selected the GL-vs-Vulkan backend. Campaign V deleted the OpenGL backend; Vulkan is the only one. Two comments still named it as a live co-requisite until 2026-08-24. | none | | `ACDREAM_ANIM_SPEED_SCALE` | Animation-speed multiplier from the pre-retail-sequencer era; died with the 1.248x factor. | none | | `ACDREAM_A8_AUDIT` | Phase A8 EnvCell batch/cull audit dump. Its only caller never existed; `EnvCellRenderer.CollectCellAuditLines` was unreachable and was deleted 2026-08-24. | `ACDREAM_PROBE_ENVCELL` | +| `ACDREAM_AIRBORNE_DIAG` | #42 airborne-sweep `[SWEEP]`/`[SWEEP-OBJ]` XY-drift trace. Investigation closed; stripped 2026-08-24 (#435) along with its 16 siblings below. | none | +| `ACDREAM_DUMP_ENTITY` | #119 tower-staircase HYDRATE/DRAW/WALK-REJECT entity watchlist. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_AUTOWALK` | Issue #63 server-initiated auto-walk trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_LIGHT` | #133 A7 dungeon-lighting `[light]`/`[light-detail]` trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_OUTSTAGE` | #131 outside-stage dynamics routing trace (also the `ACDREAM_DUMP_ENTITY` `[outstage-own]` watchlist consumer). Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_PHANTOM` | #113 phantom-shell/phantom-objs draw-mechanism trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_REACH` | #334 broadphase candidate-disposition trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_REMOTE_LANDING` | Bug A / issue #32 remote ground-contact landing trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_SHELL` | #78 cell-shell opaque-pass render trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_STEP_HEIGHTS` | Issue #338 step-up/step-down height provenance trace (including its unconditional once-per-process `AnnounceStepHeightProbeOnce` self-report). Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_STICKY` | R5-V3 issue #171 sticky-melee lifecycle/steer trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_SUPPORT` | Issue #337 `[support]`/`[geom]` collision-vs-visual classifier trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_TEXFLUSH` | #105 white-indoor-textures staged-upload trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_VIEWER` | #119-residual viewer/flood capture (tower-ascent replay). Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_PROBE_WALK_MISS` | Issue #83 indoor walkable-plane miss trace. Investigation closed; stripped 2026-08-24 (#435). | none | +| `ACDREAM_WIRE_MESH` | Issue #337 F2 overlay upgrade to real physics-BSP polygon edges. Investigation closed; stripped 2026-08-24 (#435) — F2 reverted to its proxy-cylinder overlay. | none | +| `ACDREAM_WIRE_RADIUS` | Companion radius knob for `ACDREAM_WIRE_MESH`. Stripped alongside it 2026-08-24 (#435). | none | diff --git a/src/AcDream.App/Input/PlayerModeController.cs b/src/AcDream.App/Input/PlayerModeController.cs index faa2e1d5..36665902 100644 --- a/src/AcDream.App/Input/PlayerModeController.cs +++ b/src/AcDream.App/Input/PlayerModeController.cs @@ -332,8 +332,6 @@ internal sealed class PlayerModeController : { moveTo.MoveToComplete = error => { - if (PhysicsDiagnostics.ProbeAutoWalkEnabled) - Console.WriteLine($"[autowalk-end] reason=complete err={error}"); if (error == WeenieError.None) approachLifetime.PublishNaturalCompletion(); else diff --git a/src/AcDream.App/Physics/LiveEntityMotionRuntimeController.cs b/src/AcDream.App/Physics/LiveEntityMotionRuntimeController.cs index 5a7f463d..34506cde 100644 --- a/src/AcDream.App/Physics/LiveEntityMotionRuntimeController.cs +++ b/src/AcDream.App/Physics/LiveEntityMotionRuntimeController.cs @@ -229,14 +229,6 @@ internal sealed class LiveEntityMotionRuntimeController // CObjectMaint's object table and must still resolve here. if (liveEntities.IsHidden(id)) { - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled) - { - Console.WriteLine( - $"[autowalk-host-miss] object=0x{id:X8} " - + $"materialized={_liveEntities.ContainsWorldEntity(id)} " - + $"registered={liveEntities.TryGetPhysicsHost(id, out _)} " - + $"hidden={liveEntities.IsHidden(id)}"); - } return null; } if (liveEntities.TryGetPhysicsHost(id, out var existing)) @@ -494,26 +486,6 @@ internal sealed class LiveEntityMotionRuntimeController } } movement.PerformMovement(ms); - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled) - { - string target = turnPath.TargetGuid is { } targetGuid - ? $"0x{targetGuid:X8}" : "null"; - bool targetVisible = turnPath.TargetGuid is { } visibleGuid - && _liveEntities.TryGetInteractionEligibleEntity( - visibleGuid, - out _); - bool targetHost = turnPath.TargetGuid is { } hostGuid - && _liveEntities?.TryGetPhysicsHost(hostGuid, out _) == true; - var moveTo = movement.MoveTo; - Console.WriteLine( - $"[autowalk-turn-route] wire=0x{update.MotionState.MovementType:X2} " - + $"routed={ms.Type} target={target} visible={targetVisible} " - + $"host={targetHost} stop={mp.StopCompletelyFlag} " - + $"initialized={moveTo?.Initialized ?? false} " - + $"nodes={moveTo?.PendingActions.Count() ?? 0} " - + $"command=0x{moveTo?.CurrentCommand ?? 0u:X8} " - + $"pendingMotions={movement.Minterp.MotionsPending()}"); - } return true; } diff --git a/src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs b/src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs index 040b11f1..e7722f2c 100644 --- a/src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs +++ b/src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs @@ -619,28 +619,6 @@ internal sealed class LiveEntityNetworkUpdateController // the exact swing and carries it in Commands[]. if (update.Guid == _playerServerGuid) { - // B.6 slice 1 (2026-05-14): trace inbound motion for the - // local player. One line per inbound UM, gated on - // ACDREAM_PROBE_AUTOWALK=1 (name kept through R4-V5). - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled) - { - string cmdHex = command.HasValue ? $"0x{command.Value:X4}" : "null"; - string pathStr = update.MotionState.MoveToPath is { } p - ? $"path=cell=0x{p.OriginCellId:X8},xyz=({p.OriginX:F2},{p.OriginY:F2},{p.OriginZ:F2}),minDist={p.MinDistance:F2},objDist={p.DistanceToObject:F2}" - : "path=null"; - string spd = update.MotionState.ForwardSpeed is { } fs - ? $"fwdSpd={fs:F2}" - : "fwdSpd=null"; - string mtsSpd = update.MotionState.MoveToSpeed is { } ms - ? $"mtSpd={ms:F2}" - : "mtSpd=null"; - string mtsRun = update.MotionState.MoveToRunRate is { } mr - ? $"mtRun={mr:F2}" - : "mtRun=null"; - Console.WriteLine(System.FormattableString.Invariant( - $"[autowalk-mt] stance=0x{stance:X4} cmd={cmdHex} mt=0x{update.MotionState.MovementType:X2} isMoveTo={update.MotionState.IsServerControlledMoveTo} moveTowards={update.MotionState.MoveTowards} {pathStr} {spd} {mtsSpd} {mtsRun}")); - } - // R4-V5: retail unpack_movement dispatch for the local // player — the SAME shape the remote branch uses below. // Head (@300566): interrupt + unstick fire for EVERY @@ -700,11 +678,6 @@ internal sealed class LiveEntityNetworkUpdateController } if (localDispatch.RoutedMoveTo) { - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled) - { - Console.WriteLine(System.FormattableString.Invariant( - $"[autowalk-begin] mt=0x{update.MotionState.MovementType:X2} movingTo={_playerController.Movement.IsMovingTo()} type={_playerController.MoveTo?.MovementTypeState}")); - } return; } if (!localDispatch.AppliedInterpretedState) @@ -2128,21 +2101,6 @@ internal sealed class LiveEntityNetworkUpdateController if (update.Guid == _playerServerGuid) _authorityGate.ObserveAcceptedLocalPosition(update.Position.LandblockId); - // B.6 slice 1 (2026-05-14): trace inbound UpdatePosition cadence for - // the local player. Combined with [autowalk-mt] this answers - // whether ACE's broadcast frequency during a server-initiated - // auto-walk is dense enough to drive smooth visible motion (the - // Option C viability check from the design spec). Gated on - // ACDREAM_PROBE_AUTOWALK=1; skips remote entities. - if (update.Guid == _playerServerGuid - && AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled) - { - string velStr = update.Velocity is { } v - ? $"vel=({v.X:F2},{v.Y:F2},{v.Z:F2})" - : "vel=null"; - Console.WriteLine(System.FormattableString.Invariant( - $"[autowalk-up] cell=0x{p.LandblockId:X8} pos=({p.PositionX:F2},{p.PositionY:F2},{p.PositionZ:F2}) world=({worldPos.X:F2},{worldPos.Y:F2},{worldPos.Z:F2}) {velStr} grounded={update.IsGrounded}")); - } var rot = timestampDisposition is AcDream.Core.Physics.PositionTimestampDisposition.ForcePosition ? entity.Rotation : new System.Numerics.Quaternion(p.RotationX, p.RotationY, p.RotationZ, p.RotationW); @@ -2685,14 +2643,6 @@ internal sealed class LiveEntityNetworkUpdateController // first UP after unstick (bounded by the 1 s sticky lease). bool snapSuppressedByStick = !IsPlayerGuid(update.Guid) && (rmState.Host?.PositionManager.GetStickyObjectId() ?? 0u) != 0u; - if (snapSuppressedByStick - && AcDream.Core.Physics.PhysicsDiagnostics.ProbeStickyEnabled) - { - float snapDist = System.Numerics.Vector3.Distance( - worldPos, rmState.Body.Position); - Console.WriteLine(FormattableString.Invariant( - $"[sticky-snap-skip] guid=0x{update.Guid:X8} d={snapDist:F3} srv=({worldPos.X:F2},{worldPos.Y:F2}) body=({rmState.Body.Position.X:F2},{rmState.Body.Position.Y:F2})")); - } // C4 routes 4a + 4b-2 + 4b-3 collapse: the complete near/far/ // teleport/leftover decision — including the dissolved LANDING @@ -2796,43 +2746,6 @@ internal sealed class LiveEntityNetworkUpdateController _motionRuntime.EnsureRemoteMotionBindings( rmState, aeForLand, update.Guid); } - - // Bug A investigation (2026-08-04, docs/ISSUES.md #32): - // the packet-side half of the landing capture, now fired - // for both guids (diagnostic-only — TEMPORARY, strip with - // the probe family; not behaviour). - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeRemoteLandingEnabled) - { - bool gravitySetForProbe = rmState.Body.HasGravity; - AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLanding( - site: "controller", - guid: update.Guid, - airborneBefore: true, - gravitySet: gravitySetForProbe, - contact: rmState.Body.InContact, - onWalkable: rmState.Body.OnWalkable, - hasDefaultSink: rmState.Motion.DefaultSink is not null, - resolveIsOnGround: null, - sequencerStyle: aeForLand?.Sequencer?.CurrentStyle ?? 0, - sequencerMotion: aeForLand?.Sequencer?.CurrentMotion ?? 0); - if (!gravitySetForProbe) - { - AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLandingGateNoOp( - "controller", update.Guid); - } - // Zero the sink-dispatch latches before reading them - // back — nothing at THIS site dispatches (the arming - // call lives only next to the per-tick HitGround). - AcDream.Core.Physics.PhysicsDiagnostics - .BeginRemoteLandingDispatchCapture(); - AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLandingAfter( - site: "controller", - guid: update.Guid, - hitGroundInvoked: false, - sequencerStyle: aeForLand?.Sequencer?.CurrentStyle ?? 0, - sequencerMotion: aeForLand?.Sequencer?.CurrentMotion ?? 0, - forwardCommand: rmState.Motion.InterpretedState.ForwardCommand); - } } } diff --git a/src/AcDream.App/Rendering/CollisionMeshWireframe.cs b/src/AcDream.App/Rendering/CollisionMeshWireframe.cs deleted file mode 100644 index 5954e4a8..00000000 --- a/src/AcDream.App/Rendering/CollisionMeshWireframe.cs +++ /dev/null @@ -1,372 +0,0 @@ -using System.Collections.Immutable; -using System.Numerics; -using AcDream.Core.Physics; -using AcDream.Core.Rendering; - -namespace AcDream.App.Rendering; - -/// -/// #337 collision-mesh wireframe (2026-08-06 — TEMPORARY, strip with the #337 -/// probe family). -/// -/// -/// The F2 collision overlay predating this class drew, for a BSP object, a -/// proxy cylinder sized from the object's registered BROADPHASE radius. That -/// answers "where does the collision system think this object roughly is" and -/// nothing more. The open question in Neftet is a different one — whether an -/// object's collision SURFACES are where its visual mesh is drawn — and a -/// proxy sphere cannot answer it in either direction. -/// -/// -/// -/// This draws the actual geometry instead, in three colours that are meant to -/// be read against each other: -/// -/// Cyan — the object's real physics-BSP polygon edges, in world -/// space. These are the surfaces a body can stand on or be stopped by. -/// Where the cyan mesh sits away from the rock you can see, the collision -/// is displaced; where a visible rock has no cyan on it at all, it has no -/// collision geometry. -/// Magenta — the same object's VISUAL mesh bounding box, from -/// the same prepared assets the renderer draws from. It is the reference -/// the cyan is judged against, so the comparison does not depend on the -/// eye's guess about where the visual "really" is. -/// Yellow — the outdoor terrain surface under the player, as a -/// grid of the physics engine's own sampled heights. If a body is resting -/// on the yellow rather than on cyan, terrain is holding it up and the -/// object's collision is not involved at all. -/// -/// Dim orange keeps the old broadphase proxy visible so nothing the previous -/// overlay showed has been taken away. -/// -/// -/// -/// Geometry is resolved through the SAME prepared collision accessors the -/// resolver queries (PhysicsDataCache.GetFlatGfxObj / -/// GetVisualBounds) and placed with the SAME world transform the -/// collision probes use, so this cannot draw a shape the collision system does -/// not actually hold. Reading the geometry by a second route is how AP-156 -/// managed to report a sphere the registry never emitted. -/// -/// -/// -/// Pure reads. Nothing here mutates physics, registry, or render state; the -/// caller owns the frame. -/// -/// -internal sealed class CollisionMeshWireframe -{ - // Colours, in the order the class comment lists them. - private static readonly Vector3 PhysicsColor = new(0f, 1f, 1f); - private static readonly Vector3 VisualColor = new(1f, 0f, 1f); - private static readonly Vector3 TerrainColor = new(1f, 1f, 0f); - private static readonly Vector3 BroadphaseColor = new(0.45f, 0.22f, 0f); - - /// - /// Per-frame line ceiling. Each line is 48 bytes in the debug renderer's - /// ring allocation, so this caps the overlay at ~1.9 MB a frame. Landblock - /// 0x8766 carries the largest single collision owner measured in the game - /// (an 81-cell footprint), and an uncapped walk of it would be the one - /// place this overlay falls over. - /// - private const int MaxLines = 40_000; - - /// Per-object polygon ceiling, so one enormous formation cannot - /// consume the whole budget and hide every other object near it. - private const int MaxPolygonsPerObject = 4_000; - - /// Half-width in metres of the terrain grid drawn under the - /// player, and its sample spacing. - private const float TerrainGridHalfWidth = 12f; - private const float TerrainGridStep = 2f; - - private readonly PhysicsEngine _physics; - - public CollisionMeshWireframe(PhysicsEngine physics) - => _physics = physics ?? throw new ArgumentNullException(nameof(physics)); - - /// - /// Emit the overlay for everything within - /// of - /// . Returns what it drew so the caller can - /// report a capped frame rather than silently showing partial geometry. - /// - public CollisionMeshWireframeStats Draw(DebugLineRenderer lines, Vector3 centre) - { - ArgumentNullException.ThrowIfNull(lines); - - float radius = RenderingDiagnostics.CollisionMeshWireframeRadius; - float radiusSquared = radius * radius; - var budget = new LineBudget(MaxLines); - - int objects = 0; - int polygons = 0; - int withoutGeometry = 0; - - PhysicsDataCache? cache = _physics.DataCache; - - foreach (ShadowEntry shadow in _physics.ShadowObjects.AllEntriesForDebug()) - { - // Objects register their part ORIGIN, which for a BSP part is - // routinely nowhere near the geometry itself (376 of the 973 - // installed physics-BSP parts sit further from their own bounding - // centre than half their radius). Admitting on origin distance - // ALONE would drop exactly the large displaced-centre formations - // this overlay exists to look at, so the object's own radius is - // added to the window. - float reach = radius + shadow.Radius; - if (Vector3.DistanceSquared(shadow.Position, centre) > reach * reach) - continue; - - objects++; - - if (shadow.CollisionType != ShadowCollisionType.BSP) - { - DrawBroadphaseProxy(lines, in shadow, budget); - continue; - } - - FlatGfxObjCollisionAsset? asset = cache?.GetFlatGfxObj(shadow.GfxObjId); - int drawn = DrawPhysicsPolygons(lines, in shadow, asset, centre, radiusSquared, budget); - polygons += drawn; - if (drawn == 0) withoutGeometry++; - - DrawVisualBounds(lines, in shadow, cache?.GetVisualBounds(shadow.GfxObjId), budget); - DrawBroadphaseProxy(lines, in shadow, budget); - } - - DrawTerrainGrid(lines, centre, budget); - - return new CollisionMeshWireframeStats( - ObjectsConsidered: objects, - PolygonsDrawn: polygons, - ObjectsWithoutPhysicsGeometry: withoutGeometry, - LinesDrawn: budget.Used, - Capped: budget.Capped); - } - - /// - /// Walk the object's physics BSP and emit one closed edge loop per polygon - /// the tree actually indexes. Polygons the tree does not reference are NOT - /// drawn: no query can reach them, so showing them would overstate the - /// collision surface. Returns the polygon count emitted. - /// - private static int DrawPhysicsPolygons( - DebugLineRenderer lines, - in ShadowEntry shadow, - FlatGfxObjCollisionAsset? asset, - Vector3 centre, - float radiusSquared, - LineBudget budget) - { - FlatPhysicsBsp? bsp = asset?.PhysicsBsp; - if (bsp is not { RootIndex: >= 0 } || bsp.Nodes.Length == 0) - return 0; - - FlatPolygonTable table = bsp.PolygonTable; - ImmutableArray vertices = table.Vertices; - int emitted = 0; - - foreach (FlatPhysicsBspNode node in bsp.Nodes) - { - FlatIndexRange indices = node.PolygonIndexRange; - for (int i = indices.Start; i < indices.EndExclusive; i++) - { - if (emitted >= MaxPolygonsPerObject || budget.Exhausted) - return emitted; - - int polygonIndex = bsp.PolygonIndexStream[i]; - if ((uint)polygonIndex >= (uint)table.Polygons.Length) continue; - - FlatIndexRange span = table.Polygons[polygonIndex].VertexRange; - if (span.Count < 2) continue; - - Vector3 first = ToWorld(vertices[span.Start], in shadow); - // Per-polygon distance rejection, AFTER the world transform: - // a big object admitted by the object-level window still only - // needs the faces near the player drawn. - if (Vector3.DistanceSquared(first, centre) > radiusSquared) continue; - - Vector3 previous = first; - for (int v = span.Start + 1; v < span.EndExclusive; v++) - { - Vector3 current = ToWorld(vertices[v], in shadow); - if (!budget.TryAdd()) return emitted; - lines.AddLine(previous, current, PhysicsColor); - previous = current; - } - - if (span.Count > 2) - { - if (!budget.TryAdd()) return emitted; - lines.AddLine(previous, first, PhysicsColor); - } - - emitted++; - } - } - - return emitted; - } - - /// - /// The visual mesh box, placed with the SAME transform as the physics - /// polygons above. It is drawn as the object's own rotated box (eight - /// transformed corners, twelve edges) rather than as a world-axis-aligned - /// box, so a rotated object's magenta lines still bound its actual visual. - /// - private static void DrawVisualBounds( - DebugLineRenderer lines, - in ShadowEntry shadow, - GfxObjVisualBounds? visual, - LineBudget budget) - { - if (visual is null || budget.Exhausted) return; - - Vector3 min = visual.Min; - Vector3 max = visual.Max; - Span corners = - [ - ToWorld(new Vector3(min.X, min.Y, min.Z), in shadow), - ToWorld(new Vector3(max.X, min.Y, min.Z), in shadow), - ToWorld(new Vector3(max.X, max.Y, min.Z), in shadow), - ToWorld(new Vector3(min.X, max.Y, min.Z), in shadow), - ToWorld(new Vector3(min.X, min.Y, max.Z), in shadow), - ToWorld(new Vector3(max.X, min.Y, max.Z), in shadow), - ToWorld(new Vector3(max.X, max.Y, max.Z), in shadow), - ToWorld(new Vector3(min.X, max.Y, max.Z), in shadow), - ]; - - ReadOnlySpan edges = - [ - 0, 1, 1, 2, 2, 3, 3, 0, - 4, 5, 5, 6, 6, 7, 7, 4, - 0, 4, 1, 5, 2, 6, 3, 7, - ]; - - for (int e = 0; e < edges.Length; e += 2) - { - if (!budget.TryAdd()) return; - lines.AddLine(corners[edges[e]], corners[edges[e + 1]], VisualColor); - } - } - - /// - /// The registered broadphase shape — what the pre-#337 overlay showed, and - /// what the collision system's reach filter measures against. Kept so this - /// overlay is a superset of the one it replaces. - /// - private static void DrawBroadphaseProxy( - DebugLineRenderer lines, - in ShadowEntry shadow, - LineBudget budget) - { - // AddCylinder emits a fixed 36 lines. Reserve them together so a - // partial ring cannot be drawn. - if (!budget.TryAdd(36)) return; - - if (shadow.CollisionType == ShadowCollisionType.Cylinder) - { - float height = shadow.CylHeight > 0f ? shadow.CylHeight : shadow.Radius * 2f; - lines.AddCylinder(shadow.Position, shadow.Radius, height, BroadphaseColor); - return; - } - - lines.AddCylinder( - shadow.Position - new Vector3(0f, 0f, shadow.Radius), - shadow.Radius, - shadow.Radius * 2f, - BroadphaseColor); - } - - /// - /// The terrain surface under the player, sampled through the physics - /// engine's own height resolver — the same numbers the resolver grounds - /// against, not a re-derivation. Drawn as a grid rather than as the single - /// containing triangle so the slope around the player reads at a glance. - /// - private void DrawTerrainGrid(DebugLineRenderer lines, Vector3 centre, LineBudget budget) - { - int steps = (int)(TerrainGridHalfWidth * 2f / TerrainGridStep); - float originX = centre.X - TerrainGridHalfWidth; - float originY = centre.Y - TerrainGridHalfWidth; - - for (int ix = 0; ix <= steps; ix++) - { - for (int iy = 0; iy <= steps; iy++) - { - float x = originX + ix * TerrainGridStep; - float y = originY + iy * TerrainGridStep; - float? z = _physics.SampleTerrainZ(x, y); - if (z is null) continue; - - var here = new Vector3(x, y, z.Value); - - if (ix < steps) - { - float nx = x + TerrainGridStep; - if (_physics.SampleTerrainZ(nx, y) is { } nz) - { - if (!budget.TryAdd()) return; - lines.AddLine(here, new Vector3(nx, y, nz), TerrainColor); - } - } - - if (iy < steps) - { - float ny = y + TerrainGridStep; - if (_physics.SampleTerrainZ(x, ny) is { } nz2) - { - if (!budget.TryAdd()) return; - lines.AddLine(here, new Vector3(x, ny, nz2), TerrainColor); - } - } - } - } - } - - /// - /// The one placement formula, matching the [resolve-bldg] probe's - /// world transform for a shadow part - /// (TransitionTypes.FindObjCollisionsInCell): scale in the part's - /// own frame, then rotate, then translate to the registered position. - /// - private static Vector3 ToWorld(Vector3 local, in ShadowEntry shadow) - => shadow.Position + Vector3.Transform(local * shadow.Scale, shadow.Rotation); - - /// - /// Mutable line counter shared across the draw. A class rather than a - /// struct so the per-shape helpers can be static and still share it - /// without ref-plumbing through every signature. - /// - private sealed class LineBudget(int limit) - { - public int Used { get; private set; } - - public bool Capped { get; private set; } - - public bool Exhausted => Used >= limit; - - public bool TryAdd(int count = 1) - { - if (Used + count > limit) - { - Capped = true; - return false; - } - Used += count; - return true; - } - } -} - -/// What one emitted. -/// is the interesting one: a -/// non-zero count means objects near the player carry no reachable collision -/// polygons at all. -internal readonly record struct CollisionMeshWireframeStats( - int ObjectsConsidered, - int PolygonsDrawn, - int ObjectsWithoutPhysicsGeometry, - int LinesDrawn, - bool Capped); diff --git a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs index f329013c..04708dcb 100644 --- a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs +++ b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs @@ -302,37 +302,6 @@ internal sealed class RetailPViewPassExecutor : slice, sliceIndex); - public void EmitOutStageOwner( - WorldEntity entity, - Vector3 sphereCenter, - float sphereRadius, - int sliceIndex, - bool passed) => - _diagnostics.EmitOutStageOwner( - RenderingDiagnostics.ProbeOutStageEnabled, - RenderingDiagnostics.DumpEntitySourceIds, - entity, - sphereCenter, - sphereRadius, - sliceIndex, - passed); - - public void EmitOutStageRouting( - int sliceIndex, - IReadOnlyList entities, - ViewconeCuller viewcone) => - _diagnostics.EmitOutStageRouting( - RenderingDiagnostics.ProbeOutStageEnabled, - sliceIndex, - entities, - viewcone); - - public void EmitPhantomObjects(uint cellId, int survivorCount) => - _diagnostics.EmitPhantomObjects( - RenderingDiagnostics.ProbePhantomEnabled, - cellId, - survivorCount); - public void DrawLandscapeSlice( RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context) @@ -449,11 +418,6 @@ internal sealed class RetailPViewPassExecutor : _particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds); - _diagnostics.EmitOutStageParticles( - RenderingDiagnostics.ProbeOutStageEnabled, - _particles, - _particleClassifications.Outdoor); - if (!frame.RootCell.IsOutdoorNode && _particleClassifications.Outdoor.Count > 0 && _particles is not null @@ -575,7 +539,6 @@ internal sealed class RetailPViewPassExecutor : RetailPViewFrameInput frame, RetailPViewFrameResult result) => _diagnostics.EmitRetailPViewDiagnostics( - RenderingDiagnostics.ProbeViewerEnabled, RenderingDiagnostics.ProbeVisibilityEnabled, RenderingDiagnostics.ProbeFlapEnabled, result, @@ -584,7 +547,6 @@ internal sealed class RetailPViewPassExecutor : frame.PlayerCellId, frame.CameraWorldPosition, frame.PlayerViewPosition, - frame.CameraView, frame.CameraCellResolution); private void DrawPortalDepthWrite( diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 9b515852..d39df8f8 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -699,15 +699,6 @@ public sealed class RetailPViewRenderer r); if (ownerPass) _lateParticleOwnerScratch.Add(e.Id); - // #131 owner watchlist (throwaway): ACDREAM_DUMP_ENTITY ids - // double as an ENTITY-id watchlist here — one line per watched - // outdoor-static owner per CHANGE of its cone verdict. - passes.EmitOutStageOwner( - e, - c, - r, - probeSliceIndex, - ownerPass); } } foreach (var e in _outsideStageDynamics) @@ -734,10 +725,6 @@ public sealed class RetailPViewRenderer probeSliceIndex, 0); } - passes.EmitOutStageRouting( - probeSliceIndex, - _outsideStageDynamics, - viewcone); _candidateObserver?.ObservePViewBucket( CurrentRenderPViewRoute.LandscapeOutsideDynamic, probeSliceIndex, @@ -1093,9 +1080,6 @@ public sealed class RetailPViewRenderer int survivors = _allCellStatics.Count - survivorsBefore; if (survivors > 0) _cellObjCells.Add(cellId); - - // BR-2 phantom-site probe (T3-updated): post-viewcone survivors. - passes.EmitPhantomObjects(cellId, survivors); } // ONE batched static-object draw for every visible cell (was N per-cell @@ -1210,8 +1194,6 @@ public sealed class RetailPViewRenderer private bool LegacyPartitionDiagnosticsEnabled => _partitionObserver is not null - || AcDream.Core.Rendering.RenderingDiagnostics.ProbeOutStageEnabled - || AcDream.Core.Rendering.RenderingDiagnostics.ProbePhantomEnabled || AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled || AcDream.App.Streaming.EntityVanishProbe.Enabled; @@ -1380,17 +1362,6 @@ public interface IRetailPViewPassExecutor ClipFrameAssembly clipAssembly, ClipViewSlice slice, int sliceIndex); - void EmitOutStageOwner( - WorldEntity entity, - Vector3 sphereCenter, - float sphereRadius, - int sliceIndex, - bool passed); - void EmitOutStageRouting( - int sliceIndex, - IReadOnlyList entities, - ViewconeCuller viewcone); - void EmitPhantomObjects(uint cellId, int survivorCount); void DrawLandscapeSlice(RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context); void DrawLandscapeSliceLate(RetailPViewFrameInput frame, RetailPViewLandscapeLateSliceContext context); void ClearInteriorDepth(); diff --git a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs index 16a9e887..271e00fe 100644 --- a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs +++ b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs @@ -993,48 +993,6 @@ public sealed partial class EnvCellRenderer : _lastFrameStats.TrianglesDrawn += (dc.renderData.Batches.Count > 0 ? dc.renderData.Batches[0].IndexCount / 3 : 0) * dc.count; - - // Issue #78 (2026-05-31) [shell] probe (ACDREAM_PROBE_SHELL) — THROWAWAY. - // Per opaque-pass call: totals + per visible (filtered) cell whether it is - // present in the prepared snapshot, and its geometry/flags. Answers why the - // interior walls/ceiling don't appear: NOSNAP / gfx=0 ⇒ no shell geometry - // prepared for the cell; idx>0 + zh>0 ⇒ prepared but missing bindless texture - // (invisible); idx>0 + zh=0 + tr=0 ⇒ opaque geometry drawn (fault is depth/ - // occlusion or the geometry isn't the wall). Opaque pass only (halves noise). - if (renderPass == WbRenderPass.Opaque - && AcDream.Core.Rendering.RenderingDiagnostics.ProbeShellEnabled) - { - var sb = new System.Text.StringBuilder(256); - sb.Append("[shell] filter=").Append(filter?.Count ?? -1) - .Append(" drawCalls=").Append(drawCalls.Count) - .Append(" inst=").Append(allInstances.Count) - .Append(" tris=").Append(_lastFrameStats.TrianglesDrawn); - if (filter != null) - { - foreach (var cellId in filter) - { - if (!snapshot.BatchedByCell.TryGetValue(cellId, out var gfxDict)) - { - sb.Append(" [0x").Append(cellId.ToString("X8")).Append(":NOSNAP]"); - continue; - } - int gfxN = 0, tf = 0, batch = 0, idx = 0, tr = 0, zh = 0; - foreach (var (gfxObjId, transforms) in gfxDict) - { - gfxN++; tf += transforms.Count; - var rd = _meshManager.TryGetRenderData(gfxObjId); - if (rd != null) - foreach (var b in rd.Batches) - { batch++; idx += b.IndexCount; if (b.IsTransparent) tr++; if (!b.TextureSlot.IsAssigned) zh++; } - } - sb.Append(" [0x").Append(cellId.ToString("X8")) - .Append(":gfx=").Append(gfxN).Append(" tf=").Append(tf) - .Append(" batch=").Append(batch).Append(" idx=").Append(idx) - .Append(" tr=").Append(tr).Append(" zh=").Append(zh).Append(']'); - } - } - System.Console.WriteLine(sb.ToString()); - } } } diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index f9fdbdd2..3c1f4bd9 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -780,21 +780,6 @@ public sealed partial class WbDrawDispatcher : IDisposable private readonly HashSet _missRequested = new(); private readonly HashSet _missLogged = new(); - // #119 decisive probe (2026-06-11): ACDREAM_DUMP_ENTITY one-shot entity - // dump. Keyed by entity Id; the stored signature re-emits the header line - // whenever (MeshRefs count, cache batch count, zero-translation count, - // culled) changes — e.g. the Tier-1 populate landing one frame after the - // first slow-path draw. The full per-part listing prints only on first - // sight. Inert (one Count==0 check per new entity) when the env var is - // unset. Render-thread only. - private readonly Dictionary _entityDumpSig = new(); - - // Rate limiter for [dump-entity] WALK-REJECT lines: a rejected entity - // re-tests every frame; emit the first rejection per entity then every - // 300th (~5 s at 60 fps). Static because WalkEntitiesInto is static; - // render-thread only like the walk itself. - private static readonly Dictionary _walkRejectCounts = new(); - // CPU + GPU timing for [WB-DIAG] under ACDREAM_WB_DIAG=1. The GPU samples // are written by the RHI arm's SampleRhiTimers (WbDrawDispatcher.Rhi.cs) // from the device's own timer pool; the raw-GL query-object ring that used @@ -1200,7 +1185,6 @@ public sealed partial class WbDrawDispatcher : IDisposable if (!cellInVis) { if (shellScoped) result.BuildingShellAnchorReject++; - MaybeEmitWalkRejectDump(entity, "visibleCellIds-miss"); if (isCellEntity && RenderingDiagnostics.ProbeIndoorCullEnabled && indoorProbeState!.ShouldEmit(cellProbeId)) { @@ -1226,7 +1210,6 @@ public sealed partial class WbDrawDispatcher : IDisposable if (!aabbVisible) { - MaybeEmitWalkRejectDump(entity, "frustum"); if (isCellEntity && RenderingDiagnostics.ProbeIndoorCullEnabled && indoorProbeState!.ShouldEmit(cellProbeId)) { @@ -1324,117 +1307,6 @@ public sealed partial class WbDrawDispatcher : IDisposable } } - /// - /// #119 decisive probe: rate-limited [dump-entity] WALK-REJECT line - /// for an ACDREAM_DUMP_ENTITY-targeted entity that the walk filtered - /// out (visibleCellIds gate / per-entity frustum). Absence of any DRAW dump - /// plus presence of these lines attributes "entity exists but never reaches - /// the draw loop" to the specific gate. Inert when the target set is empty. - /// - private static void MaybeEmitWalkRejectDump(WorldEntity entity, string reason) - { - var targets = RenderingDiagnostics.DumpEntitySourceIds; - if (targets.Count == 0 || !targets.Contains(entity.SourceGfxObjOrSetupId)) return; - _walkRejectCounts.TryGetValue(entity.Id, out int n); - _walkRejectCounts[entity.Id] = n + 1; - if (n % 300 != 0) return; - Console.WriteLine( - $"[dump-entity] WALK-REJECT id=0x{entity.Id:X8} src=0x{entity.SourceGfxObjOrSetupId:X8} " + - $"reason={reason} parentCell=0x{(entity.ParentCellId ?? 0u):X8} " + - $"pos=({entity.Position.X:F2},{entity.Position.Y:F2},{entity.Position.Z:F2}) n={n + 1}"); - } - - /// - /// #119 decisive probe: per-entity state dump at draw time for - /// ACDREAM_DUMP_ENTITY-targeted entities. First sight prints a - /// header + every MeshRef's GfxObj id, part-transform translation, and - /// loaded flag; afterwards a compact header re-emits only when the - /// (meshRefs, cacheBatches, zeroTranslations, culled) signature changes. - /// Discriminates H-A (hydration-time MeshRef corruption: translations - /// collapsed to ~zero / missing parts) from H-B (Tier-1 cache holding a - /// partial or stale batch set) from H-C (both healthy ⇒ draw-side compose). - /// - private void MaybeEmitEntityDump( - in RenderInstanceCandidate entity, - uint landblockId, - bool culled, - IReadOnlyList tuples) - { - var targets = RenderingDiagnostics.DumpEntitySourceIds; - if (targets.Count == 0 || !targets.Contains(entity.SourceId)) - return; - - int zeroT = 0; - int refsCount = 0; - float tzMin = float.MaxValue, tzMax = float.MinValue; - for (int i = 0; i < tuples.Count; i++) - { - RenderInstanceTuple tuple = tuples[i]; - if (tuple.Candidate.LocalEntityId != entity.LocalEntityId - || tuple.Candidate.TupleLandblockId - != entity.TupleLandblockId) - { - continue; - } - - refsCount++; - Vector3 t = tuple.MeshRef.PartTransform.Translation; - if (t.LengthSquared() < 1e-9f) zeroT++; - if (t.Z < tzMin) tzMin = t.Z; - if (t.Z > tzMax) tzMax = t.Z; - } - - int cacheBatches = -1; - int restZero = 0; - float rzMin = float.MaxValue, rzMax = float.MinValue; - if (_cache.TryGet(entity.Id, landblockId, out var cacheEntry)) - { - cacheBatches = cacheEntry!.Batches.Length; - foreach (var b in cacheEntry.Batches) - { - var t = b.RestPose.Translation; - if (t.LengthSquared() < 1e-9f) restZero++; - if (t.Z < rzMin) rzMin = t.Z; - if (t.Z > rzMax) rzMax = t.Z; - } - } - - var sig = (refsCount, cacheBatches, zeroT, culled); - bool first = !_entityDumpSig.TryGetValue(entity.Id, out var prev); - if (!first && prev == sig) return; - _entityDumpSig[entity.Id] = sig; - - string cacheStr = cacheBatches < 0 - ? (_tier1CacheDisabled ? "disabled" : "miss") - : $"hit:{cacheBatches} restZero={restZero} restZ=[{rzMin:F2}..{rzMax:F2}]"; - Console.WriteLine( - $"[dump-entity] DRAW{(first ? "" : "-CHANGED")} id=0x{entity.Id:X8} src=0x{entity.SourceId:X8} " + - $"lb=0x{landblockId:X8} cell=0x{entity.ParentCellId:X8} " + - $"pos=({entity.Position.X:F2},{entity.Position.Y:F2},{entity.Position.Z:F2}) scale={entity.Scale:F2} " + - $"meshRefs={refsCount} tZero={zeroT} tZ=[{tzMin:F2}..{tzMax:F2}] cache={cacheStr} culled={culled}"); - - if (first) - { - for (int i = 0; i < tuples.Count; i++) - { - RenderInstanceTuple tuple = tuples[i]; - if (tuple.Candidate.LocalEntityId - != entity.LocalEntityId - || tuple.Candidate.TupleLandblockId - != entity.TupleLandblockId) - { - continue; - } - - MeshRef mr = tuple.MeshRef; - var t = mr.PartTransform.Translation; - bool loaded = _meshAdapter.TryGetRenderData(mr.GfxObjId) is not null; - Console.WriteLine( - $"[dump-entity] part[{tuple.MeshRefIndex:D2}] gfx=0x{mr.GfxObjId:X8} t=({t.X:F3},{t.Y:F3},{t.Z:F3}) loaded={loaded}"); - } - } - } - public void Draw( ICamera camera, IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, @@ -1651,15 +1523,6 @@ public sealed partial class WbDrawDispatcher : IDisposable ? new Vector2(lighting.Luminosity, lighting.Diffuse) : new Vector2(0f, 1f); - // #119 decisive probe: one-shot dump (+ change re-emission) for - // ACDREAM_DUMP_ENTITY-targeted entities. Before the culled-continue - // so a routed-out entity still reports its state. - MaybeEmitEntityDump( - in entity, - cacheLb, - _currentEntityCulled, - _candidateTupleScratch); - // #176 seam-draw probe: any entity parented to a target cell reports // its position + light set (a floor-coincident static/plate would be // the z-fight's second draw; the player entity is the positive diff --git a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs index e8f7351c..0a866362 100644 --- a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs +++ b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs @@ -589,11 +589,6 @@ public sealed class WbMeshAdapter meshManager.RequeueStagedMeshData(m); meshManager.SetArenaBackpressure(arenaBackpressured); - bool texProbe = AcDream.Core.Rendering.RenderingDiagnostics.ProbeTexFlushEnabled; - var pendingBefore = texProbe - ? meshManager.GetPendingTextureUpdateStats() - : default; - // #105 root cause (2026-06-10): TextureAtlasManager.AddTexture only STAGES // immutable decoded payloads in ManagedGLTextureArray._pendingUpdates — the // actual TexSubImage3D copies + mipmap regeneration happen in @@ -643,9 +638,6 @@ public sealed class WbMeshAdapter LastBufferAllocationBytes = uploadBudget.BufferAllocationBytes; LastBufferCopyBytes = uploadBudget.BufferCopyBytes; LastNewBufferCount = uploadBudget.NewBufferCount; - - if (texProbe) - EmitTexFlushProbe(pendingBefore); } private static void RejectUnsupportedHead( @@ -663,32 +655,6 @@ public sealed class WbMeshAdapter meshManager.RejectUnsupportedStagedUpload(rejected, error); } - // #105 apparatus state — see RenderingDiagnostics.ProbeTexFlushEnabled. - private int _lastTexFlushBefore = -1; - private int _texFlushHeartbeat; - - /// - /// #105 apparatus: one [tex-flush] line on change of the staged-texture - /// pending picture (plus a ~10 s heartbeat while anything is stuck). A healthy - /// frame ends with after=0; before==after>0 persisting at - /// standstill is the white-walls mechanism live (staged uploads never applied). - /// - private void EmitTexFlushProbe((int PendingUpdates, int ArraysWithPending, int TotalArrays) before) - { - var after = _meshManager!.GetPendingTextureUpdateStats(); - bool changed = before.PendingUpdates != _lastTexFlushBefore; - bool flushed = after.PendingUpdates != before.PendingUpdates; - bool heartbeat = after.PendingUpdates > 0 && ++_texFlushHeartbeat >= 600; - if (!changed && !flushed && !heartbeat) return; - - _texFlushHeartbeat = 0; - _lastTexFlushBefore = before.PendingUpdates; - Console.WriteLine( - $"[tex-flush] before={before.PendingUpdates} after={after.PendingUpdates}" + - $" arrays={after.ArraysWithPending}/{after.TotalArrays}" + - $" (arraysBefore={before.ArraysWithPending})"); - } - /// public void Dispose() { diff --git a/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs b/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs index 1ae0191b..dede5851 100644 --- a/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs +++ b/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs @@ -51,15 +51,11 @@ internal sealed class WorldRenderDiagnostics { private readonly IRenderGlStateReader _gl; private readonly IRenderFrameDiagnosticLog _log; - private readonly HashSet _lastViewerFloodCells = []; - private readonly HashSet _outStageUnmatched = []; - private readonly HashSet _outStageMatched = []; private readonly Stopwatch _terrainStopwatch = new(); private readonly RollingTimingSampleWindow _terrainSamples = new(256); private string? _lastRenderSignature; private int _renderSignatureFrame; private int _renderSignatureStableFrames; - private string? _lastViewerSignature; private string? _lastGlStateSignature; private long _glStateFrame; private long _glStateStableFrames; @@ -68,10 +64,6 @@ internal sealed class WorldRenderDiagnostics private long _postWorldGlStateStableFrames; private string? _lastScissorSignature; private long _scissorSequence; - private string? _lastOutStageSignature; - private string? _lastOutStageRoutingSignature; - private readonly Dictionary _outStageOwnerVerdicts = []; - private readonly Dictionary _phantomObjectSignatures = []; private string? _lastClipRouteSignature; private long _clipRouteSequence; private readonly List _clipRouteCellKeys = []; @@ -258,75 +250,6 @@ internal sealed class WorldRenderDiagnostics _log.WriteLine($"[clip-route] n={_clipRouteSequence} {signature}"); } - public void EmitOutStageOwner( - bool enabled, - IReadOnlySet watchedEntityIds, - WorldEntity entity, - Vector3 sphereCenter, - float sphereRadius, - int sliceIndex, - bool passed) - { - if (!enabled - || !watchedEntityIds.Contains(entity.Id) - || (_outStageOwnerVerdicts.TryGetValue(entity.Id, out bool previous) - && previous == passed)) - { - return; - } - - _outStageOwnerVerdicts[entity.Id] = passed; - _log.WriteLine(FormattableString.Invariant( - $"[outstage-own] id=0x{entity.Id:X8} src=0x{entity.SourceGfxObjOrSetupId:X8} pos=({entity.Position.X:F1},{entity.Position.Y:F1},{entity.Position.Z:F1}) c=({sphereCenter.X:F1},{sphereCenter.Y:F1},{sphereCenter.Z:F1}) r={sphereRadius:F1} slice={sliceIndex} {(passed ? "PASS" : "CULL")}")); - } - - public void EmitOutStageRouting( - bool enabled, - int sliceIndex, - IReadOnlyList entities, - ViewconeCuller viewcone) - { - if (!enabled) - return; - - var text = new StringBuilder(192); - text.Append("slice=").Append(sliceIndex) - .Append(" outStage=").Append(entities.Count).Append(" ["); - for (int i = 0; i < entities.Count; i++) - { - WorldEntity entity = entities[i]; - EntitySphere(entity, out Vector3 center, out float radius); - bool passed = viewcone.SphereVisibleInOutsideSlice( - sliceIndex, - center, - radius); - if (i > 0) - text.Append(' '); - text.Append(FormattableString.Invariant( - $"0x{(entity.ServerGuid != 0 ? entity.ServerGuid : entity.Id):X8}(s{entity.SourceGfxObjOrSetupId:X8}):{(passed ? "PASS" : "CULL")}:r={radius:F1}")); - } - text.Append(']'); - string signature = text.ToString(); - if (signature == _lastOutStageRoutingSignature) - return; - _lastOutStageRoutingSignature = signature; - _log.WriteLine("[outstage] " + signature); - } - - public void EmitPhantomObjects(bool enabled, uint cellId, int survivorCount) - { - if (!enabled - || (_phantomObjectSignatures.TryGetValue(cellId, out int previous) - && previous == survivorCount)) - { - return; - } - - _phantomObjectSignatures[cellId] = survivorCount; - _log.WriteLine( - $"[phantom-objs] cell=0x{cellId:X8} entities={survivorCount} (drawn unclipped, no viewcone)"); - } - public void EmitSeamMask( bool enabled, IReadOnlySet targetCells, @@ -374,57 +297,7 @@ internal sealed class WorldRenderDiagnostics $"[pv-input] outRoot={root} flood={portalFrame.OrderedVisibleCells.Count} eye=({eye.X:F6},{eye.Y:F6},{eye.Z:F6}) player=({player.X:F6},{player.Y:F6},{player.Z:F6}) rawPlayer=({rawPlayer.X:F6},{rawPlayer.Y:F6},{rawPlayer.Z:F6}) yaw={yaw:F8} {terrain} vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]")); } - public void EmitOutStageParticles( - bool enabled, - ParticleSystem? particles, - IReadOnlySet ownerIds) - { - if (!enabled || particles is null) - return; - - int matched = 0; - int attached = 0; - int unattached = 0; - _outStageUnmatched.Clear(); - _outStageMatched.Clear(); - foreach (var (emitter, _) in particles.EnumerateLive()) - { - if (emitter.AttachedObjectId == 0) - { - unattached++; - continue; - } - - attached++; - if (ownerIds.Contains(emitter.AttachedObjectId)) - { - matched++; - if (_outStageMatched.Count < 48) - _outStageMatched.Add(emitter.AttachedObjectId); - } - else if (_outStageUnmatched.Count < 12) - { - _outStageUnmatched.Add(emitter.AttachedObjectId); - } - } - - var unmatched = new StringBuilder(96); - foreach (uint id in _outStageUnmatched) - unmatched.Append(FormattableString.Invariant($" 0x{id:X8}")); - var matchedIds = new StringBuilder(192); - foreach (uint id in _outStageMatched) - matchedIds.Append(FormattableString.Invariant($" 0x{id:X8}")); - string signature = FormattableString.Invariant( - $"ids={ownerIds.Count} attachedEmitters={attached} matched={matched} unattached={unattached} matchedIds=[{matchedIds}] unmatchedIds=[{unmatched}]"); - if (signature == _lastOutStageSignature) - return; - - _lastOutStageSignature = signature; - _log.WriteLine("[outstage-pt] " + signature); - } - public void EmitRetailPViewDiagnostics( - bool viewerEnabled, bool visibilityEnabled, bool flapEnabled, RetailPViewFrameResult result, @@ -433,22 +306,8 @@ internal sealed class WorldRenderDiagnostics uint playerCellId, Vector3 cameraPosition, Vector3 playerPosition, - Matrix4x4 cameraView, CameraCellResolution cameraCellResolution) { - if (viewerEnabled) - { - string signature = FormattableString.Invariant( - $"root=0x{clipRoot.CellId:X8}{(clipRoot.IsOutdoorNode ? "(OUT)" : string.Empty)} flood={result.PortalFrame.OrderedVisibleCells.Count} outPolys={result.PortalFrame.OutsideView.Polygons.Count} pCell=0x{playerCellId:X8}"); - if (signature != _lastViewerSignature) - { - _lastViewerSignature = signature; - _log.WriteLine(FormattableString.Invariant( - $"[viewer] {signature} eye=({cameraPosition.X:F3},{cameraPosition.Y:F3},{cameraPosition.Z:F3}) fwd=({-cameraView.M13:F4},{-cameraView.M23:F4},{-cameraView.M33:F4}) viewerCell=0x{viewerCellId:X8}")); - EmitViewerDiff(result.PortalFrame.OrderedVisibleCells); - } - } - if (visibilityEnabled) { AcDream.Core.Rendering.RenderingDiagnostics.EmitVis( @@ -619,49 +478,6 @@ internal sealed class WorldRenderDiagnostics + $"stencil={(state.Stencil ? 1 : 0)} " + $"clip=0x{state.ClipBits:X2} err=0x{state.Error:X}"; - private void EmitViewerDiff(IReadOnlyList current) - { - var text = new StringBuilder(96); - text.Append("[viewer-diff] added=["); - bool first = true; - foreach (uint cell in current) - { - if (_lastViewerFloodCells.Contains(cell)) - continue; - if (!first) - text.Append(','); - text.Append("0x").Append(cell.ToString("X8")); - first = false; - } - - text.Append("] removed=["); - first = true; - foreach (uint cell in _lastViewerFloodCells) - { - bool present = false; - for (int index = 0; index < current.Count; index++) - { - if (current[index] == cell) - { - present = true; - break; - } - } - if (present) - continue; - if (!first) - text.Append(','); - text.Append("0x").Append(cell.ToString("X8")); - first = false; - } - text.Append(']'); - _log.WriteLine(text.ToString()); - - _lastViewerFloodCells.Clear(); - foreach (uint cell in current) - _lastViewerFloodCells.Add(cell); - } - private static string FormatVector(Vector3 value) { static float Quantize(float component) => MathF.Round(component * 20f) / 20f; @@ -748,15 +564,4 @@ internal sealed class WorldRenderDiagnostics .Append(" live=").Append(partition.Dynamics.Count) .ToString(); } - - private static void EntitySphere( - WorldEntity entity, - out Vector3 center, - out float radius) - { - if (entity.AabbDirty) - entity.RefreshAabb(); - center = (entity.AabbMin + entity.AabbMax) * 0.5f; - radius = (entity.AabbMax - entity.AabbMin).Length() * 0.5f; - } } diff --git a/src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs b/src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs index 7fdc66ae..bbfac8d7 100644 --- a/src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs +++ b/src/AcDream.App/Rendering/WorldRenderFrameBuilder.cs @@ -513,17 +513,6 @@ internal sealed class RuntimeWorldFrameEnvironmentPreparation // (150..1500 m) fog and hid the fact that the sky pass was being // fogged at all (see SkyRenderer / sky.frag). _lightingUbo?.Upload(ubo); - - RenderingDiagnostics.EmitLight( - insideCell: roots.PlayerInsideCell, - ambientR: _lighting.CurrentAmbient.AmbientColor.X, - ambientG: _lighting.CurrentAmbient.AmbientColor.Y, - ambientB: _lighting.CurrentAmbient.AmbientColor.Z, - sunIntensity: _lighting.Sun?.Intensity ?? 0f, - registeredLights: _lighting.RegisteredCount, - activeLights: (int)ubo.CellAmbient.W, - playerCellId: roots.PlayerRoot?.CellId ?? 0u, - lights: _lighting); } public void ObserveDrawableCells(IReadOnlySet drawableCells) diff --git a/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs b/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs index e3dbb540..e36f8a0c 100644 --- a/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs +++ b/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs @@ -64,10 +64,6 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics private readonly DebugVmRenderFactsPublisher _debugVm; private readonly bool _debugVmConsumerActive; private int _debugDrawLogCount; - // #337 (TEMPORARY): built on first use so the ordinary overlay path and - // every headless/no-window host pay nothing for it. - private CollisionMeshWireframe? _meshWireframe; - private CollisionMeshWireframeStats _lastMeshStats = new(-1, -1, -1, -1, false); public WorldSceneDiagnosticsController( WorldRenderDiagnostics diagnostics, @@ -219,18 +215,6 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics if (!_state.CollisionWireframesVisible || _lines is null) return; - // #337 (2026-08-06 — TEMPORARY): ACDREAM_WIRE_MESH=1 replaces the - // broadphase-proxy overlay below with the objects' real physics-BSP - // polygon edges beside their visual mesh boxes and the terrain surface - // — see CollisionMeshWireframe for why the proxy cannot answer the - // question this mode exists for. Default off; F2 keeps its old - // behaviour otherwise. - if (RenderingDiagnostics.CollisionMeshWireframeEnabled) - { - DrawCollisionMesh(in camera); - return; - } - _lines.Begin(); int drawn = 0; foreach (ShadowEntry shadow in _physics.ShadowObjects.AllEntriesForDebug()) @@ -271,50 +255,6 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics _lines.Flush(camera.Camera.View, camera.Projection); } - /// - /// #337 (2026-08-06 — TEMPORARY, strip with the probe family). Centres on - /// the player when there is one, else on the camera, so the fly-camera - /// mode can inspect geometry too. - /// - private void DrawCollisionMesh(in WorldCameraFrame camera) - { - Vector3 centre = _mode.IsPlayerMode && _player.Controller is { } controller - ? controller.Position - : camera.Position; - - _meshWireframe ??= new CollisionMeshWireframe(_physics); - - _lines!.Begin(); - CollisionMeshWireframeStats stats = _meshWireframe.Draw(_lines, centre); - - if (_mode.IsPlayerMode && _player.Controller is { } player) - { - _lines.AddCylinder( - player.Position, - DebugVmRenderFactsPublisher.PlayerCollisionRadius, - 1.8f, - new Vector3(1f, 0f, 0f)); - } - - _lines.Flush(camera.Camera.View, camera.Projection); - - // A capped frame is showing PARTIAL geometry, which would otherwise be - // indistinguishable from an object that has none — exactly the - // misreading this overlay exists to prevent. Say so, throttled, rather - // than letting the picture lie. - if (stats != _lastMeshStats) - { - _lastMeshStats = stats; - Console.WriteLine(string.Format( - System.Globalization.CultureInfo.InvariantCulture, - "[wire-mesh] centre=({0:F2},{1:F2},{2:F2}) objects={3} polys={4} " + - "noPhysicsGeometry={5} lines={6} capped={7}", - centre.X, centre.Y, centre.Z, - stats.ObjectsConsidered, stats.PolygonsDrawn, - stats.ObjectsWithoutPhysicsGeometry, stats.LinesDrawn, stats.Capped)); - } - } - private void LogNearbyCollisionObjects(Vector3 playerPosition, int drawn) { if (_debugDrawLogCount >= 5) diff --git a/src/AcDream.App/Streaming/LandblockBuildFactory.cs b/src/AcDream.App/Streaming/LandblockBuildFactory.cs index 5e1e04cc..5654ebb8 100644 --- a/src/AcDream.App/Streaming/LandblockBuildFactory.cs +++ b/src/AcDream.App/Streaming/LandblockBuildFactory.cs @@ -557,15 +557,6 @@ public sealed class LandblockBuildFactory // Phase 2d: static objects inside the EnvCell. foreach (var stab in envCell.StaticObjects) { - // #119 decisive probe: HYDRATE-side dump for ACDREAM_DUMP_ENTITY- - // targeted stabs. This is the MOMENT MeshRefs are constructed — - // a degraded dat read here (setup null / placement frames short / - // part GfxObj null) permanently corrupts the entity (H-A), and - // nothing downstream ever rebuilds it. Inert when the set is empty. - bool dumpStab = AcDream.Core.Rendering.RenderingDiagnostics - .DumpEntitySourceIds.Contains(stab.Id); - int dumpSetupParts = -1, dumpPlacementFrames = -1, dumpFlattened = -1, dumpDropped = 0; - // #136: skip an EDITOR-ONLY placement marker. Such a dat object degrades to // nothing (GfxObj id 0) at any runtime distance, so retail's distance-based // degrade (CPhysicsPart::UpdateViewerDistance) never draws it — only the @@ -596,10 +587,6 @@ public sealed class LandblockBuildFactory if (pb is not null) interiorBounds.Add(System.Numerics.Matrix4x4.Identity, pb.Value); meshRefs.Add(new AcDream.Core.World.MeshRef(stab.Id, System.Numerics.Matrix4x4.Identity)); } - else if (dumpStab) - { - Console.WriteLine($"[dump-entity] HYDRATE src=0x{stab.Id:X8} cell=0x{envCellId:X8} GFXOBJ-NULL -> entity dropped"); - } } else if ((stab.Id & 0xFF000000u) == 0x02000000u) { @@ -608,12 +595,6 @@ public sealed class LandblockBuildFactory { stabLightCount = setup.Lights.Count; var flat = AcDream.Core.Meshing.SetupMesh.Flatten(setup); - if (dumpStab) - { - dumpSetupParts = setup.Parts.Count; - dumpPlacementFrames = setup.PlacementFrames.Count; - dumpFlattened = flat.Count; - } foreach (var mr in flat) { // #136: skip an editor-only marker PART (retail hides it at runtime @@ -625,9 +606,6 @@ public sealed class LandblockBuildFactory var gfx = _dats.Get(mr.GfxObjId); if (gfx is null) { - dumpDropped++; - if (dumpStab) - Console.WriteLine($"[dump-entity] HYDRATE src=0x{stab.Id:X8} cell=0x{envCellId:X8} part gfx=0x{mr.GfxObjId:X8} GFXOBJ-NULL -> part dropped"); continue; } var pb = AcDream.Core.Meshing.GfxObjBounds.Get(gfx); @@ -635,20 +613,12 @@ public sealed class LandblockBuildFactory meshRefs.Add(mr); } } - else if (dumpStab) - { - Console.WriteLine($"[dump-entity] HYDRATE src=0x{stab.Id:X8} cell=0x{envCellId:X8} SETUP-NULL -> entity dropped"); - } } if (!AcDream.Core.Meshing.EntityHydrationRules.ShouldKeepEntity(meshRefs.Count, stabLightCount)) { - if (dumpStab) - Console.WriteLine($"[dump-entity] HYDRATE src=0x{stab.Id:X8} cell=0x{envCellId:X8} meshRefs=0 lights=0 -> entity dropped"); continue; } - if (meshRefs.Count == 0 && dumpStab) - Console.WriteLine($"[dump-entity] HYDRATE src=0x{stab.Id:X8} cell=0x{envCellId:X8} meshRefs=0 lights={stabLightCount} -> KEPT as mesh-less light carrier"); // Stabs inside EnvCells are already in landblock-local coordinates // (same space as LandBlockInfo.Objects stabs). Adding cellOrigin would @@ -671,20 +641,6 @@ public sealed class LandblockBuildFactory if (interiorBounds.TryGet(out var ibMin, out var ibMax)) hydrated.SetLocalBounds(ibMin, ibMax); - if (dumpStab) - { - Console.WriteLine( - $"[dump-entity] HYDRATE src=0x{stab.Id:X8} cell=0x{envCellId:X8} entId=0x{hydrated.Id:X8} " + - $"setupParts={dumpSetupParts} placementFrames={dumpPlacementFrames} flattened={dumpFlattened} " + - $"built={meshRefs.Count} dropped={dumpDropped} " + - $"pos=({worldPos.X:F2},{worldPos.Y:F2},{worldPos.Z:F2})"); - for (int i = 0; i < meshRefs.Count; i++) - { - var t = meshRefs[i].PartTransform.Translation; - Console.WriteLine($"[dump-entity] hyd-part[{i:D2}] gfx=0x{meshRefs[i].GfxObjId:X8} t=({t.X:F3},{t.Y:F3},{t.Z:F3})"); - } - } - result.Add(hydrated); } } diff --git a/src/AcDream.Core/Physics/Motion/MotionTableDispatchSink.cs b/src/AcDream.Core/Physics/Motion/MotionTableDispatchSink.cs index d1bdf41f..b9e958df 100644 --- a/src/AcDream.Core/Physics/Motion/MotionTableDispatchSink.cs +++ b/src/AcDream.Core/Physics/Motion/MotionTableDispatchSink.cs @@ -34,12 +34,6 @@ public sealed class MotionTableDispatchSink : IInterpretedMotionSink public bool ApplyMotion(uint motion, float speed) { uint result = _sequencer.PerformMovement(MotionTableMovement.Interpreted(motion, speed)); - // Bug A probe ([remote-landing-after], ACDREAM_PROBE_REMOTE_LANDING): - // the MotionTableManagerError code is discarded by this bool return, - // so hand it to the diagnostic latch before it is lost. Self-guarded - // — one flag test when the probe is off, no behaviour change either - // way. TEMPORARY, strips with the rest of the probe family. - PhysicsDiagnostics.RecordRemoteLandingDispatch(motion, result); return result == MotionTableManagerError.Success; } diff --git a/src/AcDream.Core/Physics/Motion/StickyManager.cs b/src/AcDream.Core/Physics/Motion/StickyManager.cs index 1ca0308b..d0794f8f 100644 --- a/src/AcDream.Core/Physics/Motion/StickyManager.cs +++ b/src/AcDream.Core/Physics/Motion/StickyManager.cs @@ -82,10 +82,6 @@ public sealed class StickyManager if (TargetId == 0) return; - if (PhysicsDiagnostics.ProbeStickyEnabled) - Console.WriteLine(FormattableString.Invariant( - $"[sticky] guid=0x{_host.Id:X8} UNSTICK target=0x{TargetId:X8}")); - TargetId = 0; Initialized = false; _host.ClearTarget(); @@ -125,10 +121,6 @@ public sealed class StickyManager Initialized = false; StickyTimeoutTime = _host.CurTime + StickyTime; - if (PhysicsDiagnostics.ProbeStickyEnabled) - Console.WriteLine(FormattableString.Invariant( - $"[sticky] guid=0x{_host.Id:X8} STICK target=0x{objectId:X8} tgtR={targetRadius:F2} ownR={_host.Radius:F2} lease={StickyTime:F1}s")); - // set_target(context_id=0, objectId, radius=0.5, quantum=0.5). _host.SetTarget(0, objectId, 0.5f, 0.5); } @@ -149,10 +141,6 @@ public sealed class StickyManager // C0|C3 clear = cur_time > timeout; ACE `>` too), not >=. if (_host.CurTime > StickyTimeoutTime) { - if (PhysicsDiagnostics.ProbeStickyEnabled) - Console.WriteLine(FormattableString.Invariant( - $"[sticky] guid=0x{_host.Id:X8} LEASE-EXPIRE target=0x{TargetId:X8}")); - TargetId = 0; Initialized = false; _host.ClearTarget(); @@ -183,10 +171,6 @@ public sealed class StickyManager if (TargetId != 0) { - if (PhysicsDiagnostics.ProbeStickyEnabled) - Console.WriteLine(FormattableString.Invariant( - $"[sticky] guid=0x{_host.Id:X8} TARGET-{info.Status} teardown target=0x{TargetId:X8}")); - TargetId = 0; Initialized = false; _host.ClearTarget(); @@ -268,9 +252,5 @@ public sealed class StickyManager if (heading < -MoveToMath.Epsilon) heading += 360f; offset.SetHeading(heading); - - if (PhysicsDiagnostics.ProbeStickyEnabled) - Console.WriteLine(FormattableString.Invariant( - $"[sticky] guid=0x{_host.Id:X8} ADJ dist={dist:F3} delta={delta:F3} speed={speed:F1} hdgDelta={heading:F1} live={(target is not null ? 1 : 0)}")); } } diff --git a/src/AcDream.Core/Physics/PhysicsDataCache.cs b/src/AcDream.Core/Physics/PhysicsDataCache.cs index af4f3e87..f25afcf9 100644 --- a/src/AcDream.Core/Physics/PhysicsDataCache.cs +++ b/src/AcDream.Core/Physics/PhysicsDataCache.cs @@ -650,23 +650,6 @@ public sealed class PhysicsDataCache $"[cell-cache] envCellId=0x{envCellId:X8} physicsPolyCount={cellStruct.PhysicsPolygons?.Count ?? 0} resolvedCount={resolved.Count} bspTotalLeafPolys={bspTotalLeafPolys} bspUnmatchedIds={bspUnmatchedIds} {bsStr} portalCount={portals.Count} visibleCells={visibleCellIds.Count} cellBspRoot={(cellStruct.CellBSP?.Root is null ? "null" : "ok")} worldOrigin=({worldOrigin.X:F2},{worldOrigin.Y:F2},{worldOrigin.Z:F2}) {portalTargets}")); } - if (PhysicsDiagnostics.ProbeWalkMissEnabled) - { - int walkableCount = 0; - foreach (var entry in WalkMissDiagnostic.EnumerateWalkable( - resolved, PhysicsGlobals.FloorZ)) - walkableCount++; - - Console.Write(System.FormattableString.Invariant( - $"[floor-polys] cellId=0x{envCellId:X8} walkableCount={walkableCount}")); - foreach (var entry in WalkMissDiagnostic.EnumerateWalkable( - resolved, PhysicsGlobals.FloorZ)) - { - Console.Write(System.FormattableString.Invariant( - $" [id=0x{entry.PolyId:X4} nz={entry.NormalZ:F3} bbox=({entry.BboxMin.X:F2},{entry.BboxMin.Y:F2})..({entry.BboxMax.X:F2},{entry.BboxMax.Y:F2}) planeZ@center={entry.PlaneZAtBboxCenter:F3}]")); - } - Console.WriteLine(); - } } /// diff --git a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs index 57843b0b..0f3f0102 100644 --- a/src/AcDream.Core/Physics/PhysicsDiagnostics.cs +++ b/src/AcDream.Core/Physics/PhysicsDiagnostics.cs @@ -180,60 +180,6 @@ public static class PhysicsDiagnostics 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 @@ -267,143 +213,6 @@ public static class PhysicsDiagnostics $"[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 @@ -851,32 +660,6 @@ public static class PhysicsDiagnostics /// 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 / @@ -998,39 +781,6 @@ public static class PhysicsDiagnostics 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. - /// 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: @@ -1148,294 +898,6 @@ public static class PhysicsDiagnostics 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). - // CONFIRMED and CLOSED 2026-08-06: this was the cause of #337, and - // the filter is now DELETED — retail has none. `rejectedReach` is - // retained as a structurally-zero column so a post-fix capture is - // directly comparable with the pre-fix one that recorded 7,225 - // rejections on one owner, every single one with - // wouldAcceptAtCenter=True; - // (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, - /// exempt-rule - /// (rejected-reach is retired — #333 deleted the filter that - /// produced it), - /// 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. - /// What the deleted filter's admission threshold WOULD - /// have been, 2 m slack included. Since #333 no live predicate reads it; it - /// is kept so the acceptance capture shows which candidates the old filter - /// would have thrown away. - /// 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 went - /// on to a shape dispatch. Before #333 this was "and were measured by the - /// reach filter"; the filter is gone, so the two are now the same set. - /// Of those, how many the reach filter - /// rejected — outcome (a). Structurally 0 since #333 deleted the - /// filter, and retained precisely so that a post-fix capture reading 0 - /// is comparable against the pre-fix capture that read 7,225. - /// 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"; - /// /// Initial state from ACDREAM_PROBE_JUMP=1. Campaign CH user-gate /// round 2, item 1 (jump-in-air refusal reported STILL silent live after @@ -1449,500 +911,6 @@ public static class PhysicsDiagnostics public static bool ProbeJumpEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_JUMP") == "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 @@ -2140,36 +1108,18 @@ public static class PhysicsDiagnostics 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; @@ -2550,130 +1500,6 @@ public static class PhysicsDiagnostics return "?"; } - // ----------------------------------------------------------------------- - // #338 — step-height provenance - // ----------------------------------------------------------------------- - - /// - /// #338 (2026-08-06, TEMPORARY). Traces the LOCAL PLAYER's step-up / - /// step-down heights along the chain that is supposed to carry them from - /// the authored Setup to the resolver, so we can see WHERE the observed - /// 0.400 / 0.400 wins. - /// - /// - /// What is already known and does NOT need measuring: retail reads the - /// authored field (CTransition::step_up @0x0050b610 substitutes - /// object_info.step_up_height when state & 2, i.e. - /// OnWalkable, and otherwise uses 0.0399999991f), and acdream - /// already ports that gate faithfully in Transition.DoStepUp. The - /// value is the defect, not the gate. - /// - /// - /// - /// What is NOT known, and is exactly what this probe decides: the - /// controller's fields initialise to 0.4f, while - /// RuntimeSetPositionMoverPreparation genuinely computes the - /// Setup-derived value and a writer genuinely assigns it. So either the - /// prepare/publish path never runs for the local player, or it runs and - /// something later overwrites it. A live reading of 0.400 alone - /// cannot tell those apart, and a fix chosen without knowing which is a - /// coin flip. - /// - /// - /// Decision table. Read the [step-h] lines in order: - /// - /// No site=prepare line at all → the Setup-derived path - /// never runs for the local player. Fix is to wire it (and the missing - /// PlayerModeController.ApplyStepHeights the doc comment names - /// was probably it). - /// site=prepare shows 0.600/1.500 but no - /// site=publish → the command is built and never consumed on this - /// path. - /// prepare and publish both show 0.600/1.500 but - /// site=resolve shows 0.400 → a later writer clobbers it; find - /// that writer, do not re-set the field. - /// site=prepare itself shows 0.400 → the Setup lookup is - /// returning the wrong Setup, or scale is wrong. - /// site=prepare shows 0.000 → preparation.Setup.Collision - /// was null and the retail dummy path was taken. - /// - /// - /// - /// - /// Initial state from ACDREAM_PROBE_STEP_HEIGHTS=1. Zero cost when - /// off (one static bool read per site). - /// - /// - public static bool ProbeStepHeightsEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_STEP_HEIGHTS") == "1"; - - private static readonly object _stepHeightGate = new(); - private static readonly Dictionary _stepHeightSeen = new(); - - private static int _stepHeightAnnounced; - - /// - /// #338 (TEMPORARY). Prints ONCE per process, regardless of the flag, from - /// the resolve site. Two placements of this probe produced no output at - /// all, and silence cannot distinguish "the site is never reached" from - /// "the flag is false" — so the instrument reports its own state rather - /// than leaving that to inference. Reaching this call proves the site - /// executes; the printed values say whether the flag and the player filter - /// would have let anything through. - /// - public static void AnnounceStepHeightProbeOnce(bool isPlayerMover) - { - if (System.Threading.Interlocked.Exchange(ref _stepHeightAnnounced, 1) != 0) - return; - - Console.WriteLine( - $"[step-h] SELF-REPORT: resolve site reached. " - + $"ProbeStepHeightsEnabled={ProbeStepHeightsEnabled} " - + $"(ACDREAM_PROBE_STEP_HEIGHTS=" - + $"{Environment.GetEnvironmentVariable("ACDREAM_PROBE_STEP_HEIGHTS") ?? ""}) " - + $"firstMoverIsPlayer={isPlayerMover}"); - // Which BINARY is this? The 2026-08-07 wrong-checkout incident: two - // checkouts, a relative launch path, and every "test" of a fix ran a - // binary that did not contain it. Assembly identity belongs IN the - // capture, not inferred from bin timestamps afterwards. - Console.WriteLine( - $"[step-h] SELF-REPORT: assembly=" - + $"{typeof(PhysicsDiagnostics).Assembly.Location}"); - } - - /// - /// One [step-h] line. Self-guards on - /// , and is edge-triggered per site: - /// a site that keeps reporting the same pair prints once, so the - /// per-tick resolve site cannot drown the two one-shot sites it has to be - /// compared against. - /// - /// Where on the chain this reading was taken — - /// prepare, publish, or resolve. - /// The step-up height at that point, in metres. - /// The step-down height at that point. - /// Free-form provenance: the Setup id, the scale, or - /// which branch produced the value. This is what makes a surprising - /// reading actionable instead of merely surprising. - public static void LogStepHeights( - string site, float stepUp, float stepDown, string detail) - { - if (!ProbeStepHeightsEnabled) return; - - lock (_stepHeightGate) - { - if (_stepHeightSeen.TryGetValue(site, out var prev) - && prev.Up == stepUp && prev.Down == stepDown) - { - return; - } - _stepHeightSeen[site] = (stepUp, stepDown); - } - - Console.WriteLine( - $"[step-h] site={site} stepUp={stepUp:F3} stepDown={stepDown:F3} {detail}"); - } - // ------------------------------------------------------------------ // S6 (Campaign S, 2026-08-07) — AP-83/AP-91 PerfectClip TOI-tail // containment guard. diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index 0eaa5fee..103e4d20 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -1913,15 +1913,6 @@ public sealed class PhysicsEngine ? PhysicsResolveCapture.Snapshot(body) : null; - // #337 (2026-08-06 — TEMPORARY): arm the [support] probe's - // contact-plane provenance latch for this resolve, ahead of everything - // including the carried-plane seed below. The seed is itself one of - // the ten sites that assert a plane, so it stamps its own name and a - // capture can read `cpSrc=ResolveWithTransition:` as "carried - // from the body, nothing re-derived it this resolve" without needing a - // sentinel value for that case. No-op when the probe is off. - PhysicsDiagnostics.BeginContactPlaneAttribution(); - // #345 probe (2026-08-08): reset the per-tick transition-phase trace // buffer. Scoped to exactly one resolve/tick — see // PhysicsDiagnostics.BeginTransitFailTrace. No-op when the probe is @@ -1934,38 +1925,6 @@ public sealed class PhysicsEngine transition.ObjectInfo.StepUpHeight = stepUpHeight; transition.ObjectInfo.StepDownHeight = stepDownHeight; - // #338 (TEMPORARY): the resolver's own reading, taken where the - // values actually land rather than at one of two candidate call - // sites. The first attempt probed PlayerMovementController and - // printed NOTHING across 11,523 live log lines — the wrong one of - // its two resolve calls. A silent probe proves nothing, so this - // one sits where every caller must pass through. Filtered to the - // player so remotes cannot drown it. - // #338 self-report, once per process, UNCONDITIONAL. The probe has - // now been silent through two placements, and "no output" cannot - // distinguish "this site is never reached" from "the flag is - // false". This line answers both directly instead of a third round - // of inference. One Interlocked per process; strip with the probe. - PhysicsDiagnostics.AnnounceStepHeightProbeOnce( - (moverFlags & ObjectInfoState.IsPlayer) != 0); - - // The flag test MUST precede the interpolated string: this site runs - // per resolve, and building the detail eagerly cost 128 B/resolve - // with the probe OFF — caught by Slice I1's zero-allocation gate, - // which is exactly what that gate is for. - if (PhysicsDiagnostics.ProbeStepHeightsEnabled - && (moverFlags & ObjectInfoState.IsPlayer) != 0) - { - // The mover id is REQUIRED here (feedback_probe_identity_attribution): - // remote players also carry IsPlayer, and the one early - // 0.400 reading this probe caught was nearly misattributed to - // the local player for exactly that reason — it was a remote - // in its Setup-residency window (AD-68). - PhysicsDiagnostics.LogStepHeights( - "resolve", stepUpHeight, stepDownHeight, - $"mover=0x{movingEntityId:X8} onGround={isOnGround} hasBody={body is not null}"); - } - transition.ObjectInfo.StepDown = true; // Fix #42 (2026-05-05): the moving entity's ShadowEntry must be // skipped in FindObjCollisions or the sweep collides with self. @@ -2286,33 +2245,6 @@ public sealed class PhysicsEngine bool collisionNormalValid = ci.CollisionNormalValid; Vector3 collisionNormal = ci.CollisionNormal; - // #42 diagnostic (2026-05-05): trace airborne sweeps to identify the - // source of the ~1m XY drift on retail-observed stationary jumps. - // Gated on ACDREAM_AIRBORNE_DIAG=1 and !isOnGround. One line per - // resolve call. deltaXY = post - target tells us how much the sweep - // diverged from the requested target; for a clean stationary +Z - // jump we expect (0,0). cp=valid with a tilted normal would confirm - // H1 (initial-overlap depenetration → next-step AdjustOffset projects - // the +Z offset along a non-+Z normal). User repros at flat plaza / - // east hillside / north hillside; if drift direction tracks terrain - // orientation, H1 is the cause; if it tracks actor facing, H2 / H3. - if (!isOnGround - && Environment.GetEnvironmentVariable("ACDREAM_AIRBORNE_DIAG") == "1") - { - var post = sp.CheckPos; - float dx = post.X - targetPos.X; - float dy = post.Y - targetPos.Y; - string cpInfo = ci.ContactPlaneValid - ? $"valid cpN=({ci.ContactPlane.Normal.X:F3},{ci.ContactPlane.Normal.Y:F3},{ci.ContactPlane.Normal.Z:F3})" - : "none"; - Console.WriteLine( - $"[SWEEP] airborne pre=({currentPos.X:F3},{currentPos.Y:F3},{currentPos.Z:F3}) " + - $"target=({targetPos.X:F3},{targetPos.Y:F3},{targetPos.Z:F3}) " + - $"post=({post.X:F3},{post.Y:F3},{post.Z:F3}) " + - $"cell={cellId:X8}->{sp.CheckCellId:X8} ok={ok} " + - $"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 @@ -2355,67 +2287,6 @@ public sealed class PhysicsEngine $"[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}")); } - // #337 [support] probe (2026-08-06 — TEMPORARY, strip with the - // physics-probe family). Runs for EVERY body, not just the player: - // a corpse sinking through geometry is a plain physics body with - // no player-specific logic, so it is the cheapest possible control - // on whether the movement code or the geometry is at fault, and it - // is invisible to any player-filtered probe. - // - // The terrain sample below is INDEPENDENT of whatever the sweep - // decided — it asks the landblock directly what the ground height - // is under the body's own out-XY. Pairing that with the contact - // plane's height at the same XY is what separates "terrain is - // holding this body up" from "some object surface is". Read-only: - // SampleTerrainWalkable takes no locks, mutates nothing, and is - // not on the resolve's committed path. - if (PhysicsDiagnostics.ProbeSupportEnabled) - { - Vector3 outPos = sp.CheckPos; - TerrainWalkableSample? terrain = - SampleTerrainWalkable(outPos.X, outPos.Y); - - bool terrainSampled = terrain.HasValue - && PhysicsDiagnostics.TryPlaneZAt( - terrain.Value.Plane, outPos.X, outPos.Y, out _); - float terrainZ = float.NaN; - if (terrainSampled) - { - PhysicsDiagnostics.TryPlaneZAt( - terrain!.Value.Plane, outPos.X, outPos.Y, out terrainZ); - } - - PhysicsDiagnostics.LogSupport( - moverId: movingEntityId, - isPlayer: (moverFlags & ObjectInfoState.IsPlayer) != 0, - inPos: currentPos, - inCell: cellId, - targetPos: targetPos, - outPos: outPos, - outCell: sp.CheckCellId, - ok: ok, - groundedIn: isOnGround, - contact: transition.ObjectInfo.Contact, - onWalkable: transition.ObjectInfo.OnWalkable, - contactPlaneValid: ci.ContactPlaneValid, - contactPlane: ci.ContactPlane, - contactPlaneCellId: ci.ContactPlaneCellId, - contactPlaneIsWater: ci.ContactPlaneIsWater, - contactPlaneSource: PhysicsDiagnostics.ContactPlaneSource, - lastKnownValid: ci.LastKnownContactPlaneValid, - lastKnownPlane: ci.LastKnownContactPlane, - terrainSampled: terrainSampled, - terrainZ: terrainZ, - terrainNormal: terrain?.Plane.Normal ?? Vector3.Zero, - terrainCellId: terrain?.CellId ?? 0u, - terrainIsWater: terrain?.IsWater ?? false, - walkablePolygon: sp.HasWalkablePolygon, - lastWalkablePolygon: sp.HasLastWalkablePolygon, - stepUpHeight: stepUpHeight, - stepDownHeight: stepDownHeight, - velocity: body?.Velocity ?? Vector3.Zero); - } - // Phase W Stage 0 (2026-06-02): [cell-swept] probe — swept cell vs static-derived cell. // Emits before the ResolveResult is built so it shows what BOTH paths would return. // No ResolveCellId call here (it has a CellGraph.CurrCell side effect). No behavior change. diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index 051bf8c7..7c60c224 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -446,29 +446,8 @@ public sealed class CollisionInfo public void SetContactPlane( Plane plane, uint cellId, - bool isWater = false, - // #337 [support] attribution — recorded on PhysicsDiagnostics, not on - // this object; see the comment in the body for why. Compiler-supplied - // literals: no call site passes these explicitly and none needs to. - [System.Runtime.CompilerServices.CallerMemberName] string sourceMember = "", - [System.Runtime.CompilerServices.CallerLineNumber] int sourceLine = 0) + bool isWater = false) { - // #337 attribution (2026-08-06 — TEMPORARY, strip with the probe - // family). Recorded ABOVE the no-op guard on purpose: the meaning is - // "the last site that ASSERTED this plane", not "the site that first - // differed from the previous value" — a sweep that re-derives the - // identical plane it was seeded with has still told you where the - // plane comes from, and that is the fact the capture needs. - // - // It lives on PhysicsDiagnostics, NOT on this object. CollisionInfo's - // stored members are compared member-for-member by the flat/graph - // differential referee and by the scratch-reset poison test; a - // diagnostic field there is state those oracles must then be taught to - // ignore, which is how a referee stops refereeing. The latch is - // [ThreadStatic] and is armed once per resolve — see - // PhysicsDiagnostics.BeginContactPlaneAttribution. - PhysicsDiagnostics.RecordContactPlaneSource(sourceMember, sourceLine); - // A6.P3 slice 2 (2026-05-22): no-op-if-unchanged guard. Closes // issue #96 (per-tick CP-write blowup) without removing the // PhysicsEngine.cs L622 seed that step_up depends on. When the @@ -3880,35 +3859,12 @@ public sealed class Transition var oi = ObjectInfo; var ci = CollisionInfo; - // #334 candidate-disposition probe (2026-08-06 — TEMPORARY, strip with - // the physics-probe family). Filtered to the player mover so NPC / - // remote dead-reckoning resolves do not pollute the capture, matching - // PhysicsResolveCapture's filter. The zero-entry query below is - // reported EXPLICITLY: "this cell yielded nothing" is outcome (b) — - // a registration gap — and must appear as data, never as silence. - bool reachProbe = PhysicsDiagnostics.ProbeReachEnabled && oi.IsPlayer; - if (objsInCell.Count == 0) { - if (reachProbe) - PhysicsDiagnostics.LogReachQuery( - oi.SelfEntityId, cellId, sp.StepDown, - inCell: 0, exempt: 0, reached: 0, rejectedReach: 0, - noShape: 0, tested: 0, blocked: 0, - currPos: sp.GlobalCurrCenter[0].Origin); return TransitionState.OK; } - // #42 diagnostic (2026-05-05): identify which static object causes - // the airborne first-frame ~1m push. - bool airborneDiag = !oi.Contact - && Environment.GetEnvironmentVariable("ACDREAM_AIRBORNE_DIAG") == "1"; - Vector3 sphereCheckBefore = sp.CheckPos; - Vector3 checkPos = sp.GlobalSphere[0].Origin; - Vector3 currPos = sp.GlobalCurrCenter[0].Origin; - float sphereRadius = sp.GlobalSphere[0].Radius; - Vector3 movement = checkPos - currPos; // Landblock offsets feed the [resolve-bldg] probe only. engine.TryGetLandblockContext(checkPos.X, checkPos.Y, @@ -3919,38 +3875,8 @@ public sealed class Transition // registry mutations through the same reference mid-iteration. using var nearbyObjs = ShadowEntrySnapshot.Capture(objsInCell); - // #334 probe tallies — see LogReachQuery for what each one decides. - // `rejectedReach` is deliberately still REPORTED and structurally 0 - // since #333 deleted the filter: a run of the acceptance capture whose - // [reach-q] lines read rejectedReach=0 where the pre-fix capture read - // 7,225 rejections is the evidence the filter is gone, and dropping the - // column would make the two captures incomparable. - const int rRejected = 0; - int rExempt = 0, rReached = 0, rNoShape = 0, - rTested = 0, rBlocked = 0; - foreach (ShadowEntry obj in nearbyObjs.Entries) { - // #337 [geom] probe (2026-08-06 — TEMPORARY, strip with the - // physics-probe family). Emitted here, at the TOP of the candidate - // loop, so it covers every object the mover comes near regardless - // of what the exemptions and the reach filter later do with it — - // an object whose collision geometry is absent or displaced must - // be reported even when nothing ever tests it. Once per GfxObj per - // process: the line describes an ASSET, not a moment. - if (obj.CollisionType == ShadowCollisionType.BSP - && PhysicsDiagnostics.ShouldLogGeometry(obj.GfxObjId)) - { - PhysicsDiagnostics.LogGeometryFromAssets( - gfxObjId: obj.GfxObjId, - entityId: obj.EntityId, - flat: engine.DataCache.GetFlatGfxObj(obj.GfxObjId), - visual: engine.DataCache.GetVisualBounds(obj.GfxObjId), - entityWorldPosition: obj.Position, - entityScale: obj.Scale, - registeredRadius: obj.Radius); - } - // Self-skip — fix #42 (2026-05-05). Mirrors retail // CObjCell::find_obj_collisions at acclient_2013_pseudo_c.txt // 308931: `physobj != arg2->object_info.object` rejects the @@ -3965,12 +3891,6 @@ public sealed class Transition // gfxObj=0x02000001 at exactly the entity's own position). if (oi.SelfEntityId != 0 && obj.EntityId == oi.SelfEntityId) { - if (reachProbe) - { - rExempt++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "exempt-self", sphereRadius, movement.Length(), currPos); - } continue; } @@ -3979,12 +3899,6 @@ public sealed class Transition // dispatch; a true result reaches neither branch. if (oi.MissileIgnore(obj.EntityId, obj.State, obj.Flags)) { - if (reachProbe) - { - rExempt++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "exempt-missile", sphereRadius, movement.Length(), currPos); - } continue; } @@ -3993,7 +3907,6 @@ public sealed class Transition // Cell membership IS the broad phase, and the BSP walk's own root // node bounding-sphere test — correctly centred, unlike the deleted // filter — is the early-out that made this one unnecessary. - if (reachProbe) rReached++; // Commit C 2026-04-29 — retail exemption block at the top of // CPhysicsObj::FindObjCollisions @@ -4005,12 +3918,6 @@ public sealed class Transition // so this is a cheap fall-through for them. if (CollisionExemption.ShouldSkip(obj.State, obj.Flags, ObjectInfo.State)) { - if (reachProbe) - { - rExempt++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "exempt-rule", sphereRadius, movement.Length(), currPos); - } continue; } @@ -4040,12 +3947,6 @@ public sealed class Transition if (etherealForTest && sp.StepDown) { // retail pc:276799 — ethereal target not tested in step-down - if (reachProbe) - { - rExempt++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "exempt-ethereal-stepdown", sphereRadius, movement.Length(), currPos); - } continue; } sp.ObstructionEthereal = etherealForTest; @@ -4096,15 +3997,6 @@ public sealed class Transition // clear (pc:276989) fires after shape tests; we clear early here to // leave the flag clean for the next iteration. sp.ObstructionEthereal = false; - // #334 outcome (c): the entry IS a candidate, the reach - // filter DID admit it, and it still contributes nothing - // because no usable physics BSP resolved for its GfxObj. - if (reachProbe) - { - rNoShape++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "no-shape", sphereRadius, movement.Length(), currPos); - } continue; } @@ -4172,12 +4064,6 @@ public sealed class Transition Console.WriteLine(System.FormattableString.Invariant( $"[sph-skip-bsp] obj=0x{obj.EntityId:X8} state=0x{obj.State:X8} — HAS_PHYSICS_BSP_PS dispatches BSP-only")); } - if (reachProbe) - { - rExempt++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "bsp-only-skip", sphereRadius, movement.Length(), currPos); - } continue; } @@ -4221,12 +4107,6 @@ public sealed class Transition Console.WriteLine(System.FormattableString.Invariant( $"[cyl-skip-bsp] obj=0x{obj.EntityId:X8} state=0x{obj.State:X8} — HAS_PHYSICS_BSP_PS dispatches BSP-only")); } - if (reachProbe) - { - rExempt++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - "bsp-only-skip", sphereRadius, movement.Length(), currPos); - } continue; } @@ -4390,138 +4270,15 @@ public sealed class Transition // per-object test body before the outer loop continues. sp.ObstructionEthereal = false; - // #334: this candidate actually reached a shape test. `result` is - // post-Layer-2, i.e. what the query will really act on. - if (reachProbe) - { - rTested++; - if (result != TransitionState.OK) rBlocked++; - ProbeReachCandidate(engine, oi, sp, cellId, in obj, - result switch - { - TransitionState.OK => "tested-ok", - TransitionState.Collided => "tested-collided", - TransitionState.Adjusted => "tested-adjusted", - TransitionState.Slid => "tested-slid", - _ => "tested-invalid", - }, - sphereRadius, movement.Length(), currPos); - } - if (result != TransitionState.OK) { - if (airborneDiag) - { - var sphereCheckAfter = sp.CheckPos; - var d = sphereCheckAfter - sphereCheckBefore; - Console.WriteLine( - $"[SWEEP-OBJ] type={obj.CollisionType} gfxObj=0x{obj.GfxObjId:X8} " + - $"objPos=({obj.Position.X:F3},{obj.Position.Y:F3},{obj.Position.Z:F3}) " + - $"objR={obj.Radius:F3} cylH={obj.CylHeight:F3} " + - $"state={result} pushDelta=({d.X:F3},{d.Y:F3},{d.Z:F3})"); - } - // #334 (TEMPORARY): the early exit is a real query outcome, so - // it must be summarised too — otherwise the summary would - // under-report exactly the queries where something DID block, - // and "blocked" is the control that proves the probe can see a - // working collision as well as a missing one. - if (reachProbe) - PhysicsDiagnostics.LogReachQuery( - oi.SelfEntityId, cellId, sp.StepDown, - nearbyObjs.Entries.Length, rExempt, rReached, - rRejected, rNoShape, rTested, rBlocked, currPos); return result; } } - if (reachProbe) - PhysicsDiagnostics.LogReachQuery( - oi.SelfEntityId, cellId, sp.StepDown, - nearbyObjs.Entries.Length, rExempt, rReached, - rRejected, rNoShape, rTested, rBlocked, currPos); - return TransitionState.OK; } - /// - /// #334 candidate-disposition probe helper (2026-08-06 — TEMPORARY, strip - /// with the physics-probe family). Static, and takes everything by - /// parameter, so it introduces no closure display class into - /// — Slice I1's 0 B/resolve budget - /// must hold with the probe compiled in and switched off. - /// - /// - /// The target's physics-BSP ROOT sphere is resolved through the SAME - /// production accessor registration used - /// (GetFlatGfxObj(id).PhysicsBsp's root node, per - /// LiveEntityCollisionBuilder and ShadowShapeBuilder), so the - /// probe cannot report geometry that differs from what the registry - /// actually emitted. AP-156's lesson was exactly that: one resolver. - /// - /// - private static void ProbeReachCandidate( - PhysicsEngine engine, - ObjectInfo oi, - SpherePath sp, - uint cellId, - in ShadowEntry obj, - string disposition, - float sphereRadius, - float movementLen, - Vector3 currPos) - { - bool xyOnly = obj.CollisionType == ShadowCollisionType.Cylinder; - - Vector3 dOrigin = currPos - obj.Position; - float distOrigin = xyOnly - ? MathF.Sqrt(dOrigin.X * dOrigin.X + dOrigin.Y * dOrigin.Y) - : dOrigin.Length(); - - // World-space offset from the part ORIGIN (what the DELETED filter - // measured against) to the BSP root sphere CENTRE (what it should have - // measured against). Both distances are still emitted after #333: the - // pair is what proved the diagnosis, and it stays comparable across the - // pre-fix and post-fix captures. Zero for non-BSP shapes, whose - // Position already IS their centre. - Vector3 bspCentreOffset = Vector3.Zero; - if (obj.CollisionType == ShadowCollisionType.BSP) - { - var flatBsp = engine.DataCache?.GetFlatGfxObj(obj.GfxObjId)?.PhysicsBsp; - if (flatBsp is { RootIndex: >= 0 }) - { - bspCentreOffset = Vector3.Transform( - flatBsp.Nodes[flatBsp.RootIndex].BoundingSphere.Origin * obj.Scale, - obj.Rotation); - } - } - - Vector3 dCentre = currPos - (obj.Position + bspCentreOffset); - float distCentre = xyOnly - ? MathF.Sqrt(dCentre.X * dCentre.X + dCentre.Y * dCentre.Y) - : dCentre.Length(); - - float budget = sphereRadius + obj.Radius + movementLen + 2f; - - PhysicsDiagnostics.LogReachCandidate( - moverId: oi.SelfEntityId, - entityId: obj.EntityId, - gfxObjId: obj.GfxObjId, - cellId: cellId, - shape: obj.CollisionType, - disposition: disposition, - stepDown: sp.StepDown, - distOrigin: distOrigin, - distCenter: distCentre, - objRadius: obj.Radius, - sphereRadius: sphereRadius, - movementLen: movementLen, - budget: budget, - centerBudget: budget - 2f, - objPos: obj.Position, - bspCentreOffset: bspCentreOffset, - currPos: currPos); - } - /// /// BR-7 / A6.P4 (2026-06-11). The retail BUILDING collision channel — /// CSortCell::find_collisions (Ghidra 0x005340a0): an outdoor diff --git a/src/AcDream.Core/Physics/WalkMissDiagnostic.cs b/src/AcDream.Core/Physics/WalkMissDiagnostic.cs deleted file mode 100644 index 35a7dcfa..00000000 --- a/src/AcDream.Core/Physics/WalkMissDiagnostic.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Collections.Generic; -using System.Numerics; - -namespace AcDream.Core.Physics; - -/// -/// ISSUES #83 H-disambiguation spike (2026-05-21). Pure-function -/// aggregator over a dict — picks -/// the nearest walkable-eligible polygon to a given foot position -/// (cell-local space) and reports XY-containment + vertical gap so -/// the [walk-miss] emission site can disambiguate H1/H2/H3 -/// without re-walking the dictionary itself. -/// -/// -/// Also enumerates walkable polygons for the one-shot -/// [floor-polys] dump at cell-cache time. -/// -/// -/// -/// Spec: docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md. -/// -/// -public static class WalkMissDiagnostic -{ - public readonly struct AggregateResult - { - public bool Found { get; init; } - public ushort PolyId { get; init; } - public bool ContainsFootXY { get; init; } - public float Dz { get; init; } - public float NormalZ { get; init; } - } - - public readonly struct WalkableEntry - { - public ushort PolyId { get; init; } - public float NormalZ { get; init; } - public Vector3 BboxMin { get; init; } - public Vector3 BboxMax { get; init; } - public float PlaneZAtBboxCenter { get; init; } - } - - /// - /// Walks , considering only polygons - /// whose plane normal Z is at least - /// (walkable slope). Selection rule: - /// - /// Polygons whose local-XY bounding box contains - /// 's XY are preferred. Among them, - /// the one with smallest |dz| wins. - /// If no poly contains the foot XY, the poly - /// with smallest |dz| across all walkable polys wins, - /// and is false. - /// - /// - public static AggregateResult AggregateNearestWalkable( - IReadOnlyDictionary resolved, - Vector3 footLocal, - float floorZ) - { - bool bestFound = false; - bool bestContainsFootXY = false; - ushort bestPolyId = 0; - float bestAbsDz = float.MaxValue; - float bestSignedDz = 0f; - float bestNormalZ = 0f; - - foreach (var kvp in resolved) - { - var poly = kvp.Value; - if (poly.Plane.Normal.Z < floorZ) continue; - if (poly.Vertices.Length < 3) continue; - - // Local-XY bounding box. - float minX = float.MaxValue, minY = float.MaxValue; - float maxX = float.MinValue, maxY = float.MinValue; - for (int i = 0; i < poly.Vertices.Length; i++) - { - var v = poly.Vertices[i]; - if (v.X < minX) minX = v.X; - if (v.Y < minY) minY = v.Y; - if (v.X > maxX) maxX = v.X; - if (v.Y > maxY) maxY = v.Y; - } - bool containsFootXY = - footLocal.X >= minX && footLocal.X <= maxX && - footLocal.Y >= minY && footLocal.Y <= maxY; - - // Signed vertical gap from foot to the polygon's plane at - // the foot's XY: plane.D + n.x*X + n.y*Y + n.z*Z = 0 - // => planeZ = -(D + n.x*X + n.y*Y) / n.z - // => dz = footZ - planeZ - float planeZ = -(poly.Plane.D - + poly.Plane.Normal.X * footLocal.X - + poly.Plane.Normal.Y * footLocal.Y) - / poly.Plane.Normal.Z; - float signedDz = footLocal.Z - planeZ; - float absDz = MathF.Abs(signedDz); - - // Preference: prefer XY-containing polys. Among the - // preferred set, smallest |dz| wins. - bool preferOver = !bestFound - || (containsFootXY && !bestContainsFootXY) - || (containsFootXY == bestContainsFootXY && absDz < bestAbsDz); - - if (preferOver) - { - bestFound = true; - bestContainsFootXY = containsFootXY; - bestPolyId = kvp.Key; - bestAbsDz = absDz; - bestSignedDz = signedDz; - bestNormalZ = poly.Plane.Normal.Z; - } - } - - return new AggregateResult - { - Found = bestFound, - PolyId = bestPolyId, - ContainsFootXY = bestContainsFootXY, - Dz = bestSignedDz, - NormalZ = bestNormalZ, - }; - } - - /// - /// Enumerates walkable-eligible polygons (normal Z >= floorZ) - /// with their local-XY bounding boxes and plane Z at the bbox - /// center. Used by the one-shot [floor-polys] cell-load - /// dump. - /// - public static IEnumerable EnumerateWalkable( - IReadOnlyDictionary resolved, - float floorZ) - { - foreach (var kvp in resolved) - { - var poly = kvp.Value; - if (poly.Plane.Normal.Z < floorZ) continue; - if (poly.Vertices.Length < 3) continue; - - float minX = float.MaxValue, minY = float.MaxValue, minZ = float.MaxValue; - float maxX = float.MinValue, maxY = float.MinValue, maxZ = float.MinValue; - for (int i = 0; i < poly.Vertices.Length; i++) - { - var v = poly.Vertices[i]; - if (v.X < minX) minX = v.X; - if (v.Y < minY) minY = v.Y; - if (v.Z < minZ) minZ = v.Z; - if (v.X > maxX) maxX = v.X; - if (v.Y > maxY) maxY = v.Y; - if (v.Z > maxZ) maxZ = v.Z; - } - - float cx = (minX + maxX) * 0.5f; - float cy = (minY + maxY) * 0.5f; - float planeZAtCenter = -(poly.Plane.D - + poly.Plane.Normal.X * cx - + poly.Plane.Normal.Y * cy) - / poly.Plane.Normal.Z; - - yield return new WalkableEntry - { - PolyId = kvp.Key, - NormalZ = poly.Plane.Normal.Z, - BboxMin = new Vector3(minX, minY, minZ), - BboxMax = new Vector3(maxX, maxY, maxZ), - PlaneZAtBboxCenter = planeZAtCenter, - }; - } - } -} diff --git a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs index 578f9246..634627f4 100644 --- a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs +++ b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs @@ -99,33 +99,6 @@ public static class RenderingDiagnostics public static bool ProbeVisibilityEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIS") == "1"; - /// - /// #119-residual viewer/flood capture (2026-06-11): one [viewer] - /// line per CHANGE of (root cell, flood size, OutsideView poly count, - /// player cell), with the projection EYE at mm precision on every line — - /// the capture half of the tower-ascent capture→replay loop - /// (TowerAscentReplayTests replays the captured pairs deterministically). - /// Light: silent while the visibility state is stable; a tower climb - /// emits a few dozen lines. Initial state from - /// ACDREAM_PROBE_VIEWER=1. - /// - public static bool ProbeViewerEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIEWER") == "1"; - - /// - /// #131 (2026-06-12) outside-stage dynamics probe. When true, the renderer - /// emits one [outstage] line per CHANGE of the outside-stage - /// routing + per-slice cone verdict set under an interior root (which - /// outdoor dynamics were routed to the landscape slice, which survived the - /// slice viewcone), and GameWindow emits one [outstage-pt] line per - /// change of the slice Scene-particle id set + matched-emitter count. - /// Built for the portal-swirl-missing-through-doorway capture. Light: - /// silent while the set is stable. Initial state from - /// ACDREAM_PROBE_OUTSTAGE=1. - /// - public static bool ProbeOutStageEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_OUTSTAGE") == "1"; - /// /// Phase U.4c (2026-05-31) flap-convergence probe. When true, the portal /// visibility pass emits, EVERY frame the camera root is an indoor cell, a @@ -144,21 +117,6 @@ public static class RenderingDiagnostics public static bool ProbeFlapEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_FLAP") == "1"; - /// - /// Issue #78 (2026-05-31) cell-shell render probe. When true, - /// EnvCellRenderer.Render emits one [shell] line per opaque-pass - /// call: per visible (filtered) cell — is it present in the prepared snapshot, - /// how many gfxObjs + instances, and per-gfxObj batch count / index count / - /// translucent / zero-bindless-handle (missing texture) — plus the pass totals. - /// This directly answers WHY interior walls/ceilings don't appear: no geometry - /// prepared for the cell (cell absent / 0 instances), drawn-but-invisible - /// (zeroHandle / translucent against the clear color), or prepared+drawn (so the - /// fault is elsewhere — depth/occlusion). Throwaway apparatus — strip once the - /// indoor-enclosure render is fixed. Initial state from ACDREAM_PROBE_SHELL=1. - /// - public static bool ProbeShellEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_SHELL") == "1"; - /// /// Flap root-cause apparatus (2026-06-07). When true, the indoor render path emits ONE /// [pv-input] line per frame with the EXACT PortalVisibilityBuilder.Build inputs at HIGH @@ -202,21 +160,6 @@ public static class RenderingDiagnostics public static bool ProbeClipRouteEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CLIPROUTE") == "1"; - /// - /// #105 white-indoor-textures apparatus (2026-06-10). When true, WbMeshAdapter.Tick - /// emits one [tex-flush] line whenever the staged-texture-update picture changes: - /// pending layer updates across all shared atlases BEFORE and AFTER the per-frame - /// ObjectMeshManager.GenerateMipmaps() flush, plus arrays-with-pending / total-array - /// counts. The broken contract this pins: TextureAtlasManager.AddTexture only STAGES - /// pixel data (PBO + pending list); without the per-frame flush (WB GameScene.cs:975) the - /// data never reaches the GL texture and the batch samples undefined content behind a valid - /// bindless handle — the classic white walls. A healthy run shows after=0 on every - /// line; a stuck before==after>0 at standstill is the #105 mechanism live. - /// Initial state from ACDREAM_PROBE_TEXFLUSH=1. - /// - public static bool ProbeTexFlushEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_TEXFLUSH") == "1"; - /// /// Bounded-propagation port apparatus (2026-06-08). When true, PortalVisibilityBuilder.Build emits /// one [portal-churn] summary line per call: per-cell pop count (re-pops = churn), total re-enqueues, @@ -228,52 +171,6 @@ public static class RenderingDiagnostics public static bool ProbePortalChurnEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_PORTAL_CHURN") == "1"; - /// - /// BR-2 phantom-site probe (2026-06-11; plan - /// docs/plans/2026-06-11-building-render-port-plan.md §BR-2 first - /// task). The BR-1 pre-check proved the #113 phantom residual cannot be - /// GfxObj portal fills (never extracted); the surviving suspects are - /// cell-side. When true, RetailPViewRenderer emits, print-on-change - /// per cell: [phantom-shell] — per shell-pass cell, the clip-enable - /// state and each drawn slice's slot + plane count, flagging the pass-all - /// cases (NoClipSlice fallback for slot-less cells; assembler slot-0 - /// scissor fallback) — and [phantom-objs] — per object-list cell, - /// the entity-bucket size drawn unclipped/un-viewcone'd. Reproducing the - /// phantom with this on pins which mechanism draws it (shells → BR-2/BR-3; - /// statics → BR-5). Throwaway apparatus — strip when the phantom closes. - /// Initial state from ACDREAM_PROBE_PHANTOM=1. - /// - public static bool ProbePhantomEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_PHANTOM") == "1"; - - /// - /// #133 A7 (2026-06-13) dungeon-lighting objective probe. When true, - /// the per-frame scene-lighting build emits ONE [light] line - /// roughly every second (wall-clock rate-limited like WB-DIAG) via - /// : - /// - /// [light] insideCell=<bool> ambient=(r,g,b) sun=<intensity> - /// registeredLights=<N> activeLights=<uCellAmbient.w> playerCell=0x<id> - /// - /// This is the self-verification signal for the dungeon-dim question: - /// - /// insideCell=true ambient=(0.20,0.20,0.20) sun=0 - /// confirms the indoor branch fired (retail flat ambient, sun killed). - /// registeredLights is the count of dat-baked - /// point/spot lights (Setup.Lights) registered with the - /// LightManager — if this is 0 in a dungeon, the cell's static - /// objects carry no baked torches (so the only illumination IS the - /// 0.2 ambient → dim). - /// activeLights is uCellAmbient.w — the - /// shader's active-slot count, which INCLUDES the (zeroed) sun slot - /// indoors. So activeLights=1 registeredLights=0 = "only the dead - /// sun slot, no torches in range". - /// - /// Output-only, inert when off. Initial state from ACDREAM_PROBE_LIGHT=1. - /// - public static bool ProbeLightEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_PROBE_LIGHT") == "1"; - /// /// A7.L1 (2026-07-06) light-pool SET-COMPOSITION probe — the apparatus the /// [light] counts could not provide (the #176/#177 discriminator: the bug @@ -496,94 +393,8 @@ public static class RenderingDiagnostics /// internal static void ResetVisibilityProbeForTests() => _lastVisRootCellId = 0; - // Wall-clock rate-limit gate for EmitLight. Ticks (100 ns) is plenty — - // we only need ~1 Hz and avoid a Stopwatch allocation/field. Sentinel 0 - // = "never emitted" so the first call always fires. - private static long _lastLightEmitTicks; private const long LightEmitIntervalTicks = 10_000_000; // 1 s in 100-ns ticks - /// - /// #133 A7 — emit ONE rate-limited [light] line describing the - /// current scene-lighting state, followed (when - /// is supplied) by up to three [light-detail] lines for the nearest - /// ACTIVE point/spot lights. Cheap no-op when - /// is false; otherwise fires at most - /// once per second. Pull the values from the spot where - /// GameWindow.UpdateSunFromSky set Lighting.CurrentAmbient - /// / Lighting.Sun and where SceneLightingUbo.Build computed - /// the active-slot count. - /// - /// The [light-detail] lines are the answer to the "candle-spotlight" - /// question — they expose each torch's REAL dat-derived runtime values - /// (range= Falloff metres, intensity=, cone= radians, - /// color=, distToViewer=) so it is visible in launch.log - /// whether dungeon torches are tiny-range points or wide cones and at what - /// intensity — without a screenshot: - /// - /// [light-detail] kind=Point range=<Falloff m> intensity=<I> cone=<rad> color=(r,g,b) distToViewer=<m> - /// - /// - /// - /// The playerInsideCell value driving the indoor branch. - /// Cell ambient red (xyz of uCellAmbient). - /// Cell ambient green. - /// Cell ambient blue. - /// The sun LightSource.Intensity (0 indoors). - /// Total point/spot lights registered with the LightManager. - /// uCellAmbient.w — shader active-slot count (includes the zeroed sun slot indoors). - /// The player's current cell id (0 if unresolved → outside). - /// The ticked LightManager (its Active list, sorted nearest-first by the - /// just-completed Tick). When non-null, drives the [light-detail] lines. Optional so existing call - /// sites / tests that only want the aggregate line keep compiling. - public static void EmitLight(bool insideCell, - float ambientR, float ambientG, float ambientB, - float sunIntensity, - int registeredLights, - int activeLights, - uint playerCellId, - AcDream.Core.Lighting.LightManager? lights = null) - { - if (!ProbeLightEnabled) return; - - long now = DateTime.UtcNow.Ticks; - if (_lastLightEmitTicks != 0 && (now - _lastLightEmitTicks) < LightEmitIntervalTicks) - return; - _lastLightEmitTicks = now; - - var ci = System.Globalization.CultureInfo.InvariantCulture; - Console.WriteLine(string.Format(ci, - "[light] insideCell={0} ambient=({1:0.###},{2:0.###},{3:0.###}) sun={4:0.###} registeredLights={5} activeLights={6} playerCell=0x{7:X8}", - insideCell, ambientR, ambientG, ambientB, sunIntensity, - registeredLights, activeLights, playerCellId)); - - // #133 A7 (2026-06-13) — per-light detail for the "spotlight bubble" - // question. Dump the actual runtime dat-derived values of the nearest - // ~3 ACTIVE point/spot lights so the real Falloff/Intensity/ConeAngle - // are visible in launch.log (are torch ranges 1m or 10m? points or - // spots? what intensity?). The sun (Directional, slot 0) is skipped — - // it carries no Range/cone meaning. DistSq is already cached by - // LightManager.Tick this frame, so the active list is sorted nearest- - // first; we just take the first few non-directional entries. - if (lights is null) return; - var active = lights.Active; - int shown = 0; - const int MaxDetail = 3; - for (int i = 0; i < active.Length && shown < MaxDetail; i++) - { - var ls = active[i]; - if (ls is null) continue; - if (ls.Kind == AcDream.Core.Lighting.LightKind.Directional) continue; - - float dist = ls.DistSq >= 0f ? MathF.Sqrt(ls.DistSq) : 0f; - Console.WriteLine(string.Format(ci, - "[light-detail] kind={0} range={1:0.###} intensity={2:0.###} cone={3:0.####} color=({4:0.###},{5:0.###},{6:0.###}) distToViewer={7:0.###} owner=0x{8:X8} cell=0x{9:X8} dyn={10}", - ls.Kind, ls.Range, ls.Intensity, ls.ConeAngle, - ls.ColorLinear.X, ls.ColorLinear.Y, ls.ColorLinear.Z, dist, - ls.OwnerId, ls.CellId, ls.IsDynamic ? 1 : 0)); - shown++; - } - } - // Wall-clock rate-limit gate for EmitIndoorLight (shares the 1 s interval). private static long _lastIndoorLightEmitTicks; @@ -694,27 +505,6 @@ public static class RenderingDiagnostics /// public static bool IsEnvCellId(ulong id) => (id & 0xFFFFu) >= 0x0100u; - /// - /// #119 tower-staircase decisive probe (2026-06-11). Comma-separated - /// Setup / GfxObj source ids (hex, optional 0x prefix) from - /// ACDREAM_DUMP_ENTITY. Any WorldEntity whose - /// SourceGfxObjOrSetupId is in this set emits: - /// (a) a [dump-entity] HYDRATE dump at MeshRef construction time - /// (GameWindow.BuildInteriorEntitiesForStreaming) — per-part - /// placement-frame translations + dropped-part accounting — discriminating - /// hydration-time corruption (H-A: SetupMesh.Flatten identity fallback / - /// silent gfx-null part drops under degraded dat reads); - /// (b) a [dump-entity] DRAW dump in WbDrawDispatcher at first - /// draw — live MeshRefs translations + Tier-1 classification cache state — - /// re-emitted compactly whenever that state changes (H-B: stale/partial - /// cached batch set); and - /// (c) rate-limited [dump-entity] WALK-REJECT lines when the - /// dispatcher's walk filters the entity out (absence-of-draw attribution). - /// Empty set = probe off; every call site early-outs on Count == 0. - /// - public static IReadOnlySet DumpEntitySourceIds { get; } = - ParseDumpEntityIds(Environment.GetEnvironmentVariable("ACDREAM_DUMP_ENTITY")); - /// /// Parse the ACDREAM_DUMP_ENTITY value: comma-separated hex ids, /// optional 0x prefix, whitespace tolerated, malformed segments ignored @@ -794,52 +584,4 @@ public static class RenderingDiagnostics /// public static string? FrameHistoryPath { get; } = Environment.GetEnvironmentVariable("ACDREAM_FRAME_HISTORY"); - - // ── #337 collision-mesh wireframe (2026-08-06 — TEMPORARY) ────────────── - // - // The F2 collision overlay already existed, but for a BSP object it drew a - // proxy cylinder sized from the REGISTERED BROADPHASE RADIUS. That shows - // where the collision system thinks the object roughly is; it cannot show - // where the collision SURFACES are, which is the only thing that answers - // "is the collision geometry where the visual geometry is". The knobs - // below turn F2 into the real answer: the actual physics-BSP polygon - // edges, in world space, next to the same object's visual mesh box. - // - // Off by default, so F2 keeps its old cheap behaviour for anyone who wants - // it and this costs nothing until asked for. - - /// - /// When true, the F2 collision overlay draws each nearby object's REAL - /// physics-BSP polygon edges (cyan) and, beside them, the same object's - /// visual mesh bounding box (magenta), plus the terrain triangle under the - /// player (yellow). Any separation between the cyan surfaces and the - /// object you can see is the "collision is not where the visual is" - /// defect, read directly off the screen instead of inferred from a log. - /// Initial state from ACDREAM_WIRE_MESH=1. - /// TEMPORARY — strip with the #337 probe family. - /// - public static bool CollisionMeshWireframeEnabled { get; set; } = - Environment.GetEnvironmentVariable("ACDREAM_WIRE_MESH") == "1"; - - /// - /// Radius in metres around the player within which - /// resolves polygon geometry. - /// A whole landblock of rock is far more geometry than a line list wants; - /// 30 m covers everything you can wedge against. Override with - /// ACDREAM_WIRE_RADIUS=<metres>. - /// - public static float CollisionMeshWireframeRadius { get; set; } = - ParsePositiveFloat( - Environment.GetEnvironmentVariable("ACDREAM_WIRE_RADIUS"), - fallback: 30f); - - private static float ParsePositiveFloat(string? raw, float fallback) - => float.TryParse( - raw, - System.Globalization.NumberStyles.Float, - System.Globalization.CultureInfo.InvariantCulture, - out float value) - && value > 0f - ? value - : fallback; } diff --git a/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs b/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs index 19f30dfb..727ac4db 100644 --- a/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs +++ b/src/AcDream.Runtime/Gameplay/RuntimeLocalPlayerPhysicsPublicationState.cs @@ -215,14 +215,6 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable controller.StepUpHeight = command.Physics.StepUpHeight; controller.StepDownHeight = command.Physics.StepDownHeight; - // #338 (TEMPORARY): second of three readings. If `prepare` printed the - // authored pair and this line does not appear, the command is built - // and never consumed on the local player's path. - PhysicsDiagnostics.LogStepHeights( - "publish", - command.Physics.StepUpHeight, - command.Physics.StepDownHeight, - $"localEntityId={controller.LocalEntityId} scale={command.Physics.Scale:F3}"); controller.SphereList = command.Physics.Spheres; controller.ObjectScale = command.Physics.Scale; controller.PreparePositionForCommit( @@ -320,35 +312,16 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable // the PhysicsDiagnostics owner exactly as before. handleUpdateTarget: info => { - if (PhysicsDiagnostics.ProbeAutoWalkEnabled) - { - Console.WriteLine( - $"[autowalk-target] object=0x{info.ObjectId:X8} " - + $"status={info.Status} context={info.ContextId} " - + $"target=({info.TargetPosition.Frame.Origin.X:F2}," - + $"{info.TargetPosition.Frame.Origin.Y:F2}," - + $"{info.TargetPosition.Frame.Origin.Z:F2})"); - } movement.HandleUpdateTarget(info); }, interruptCurrentMovement: () => { - if (PhysicsDiagnostics.ProbeAutoWalkEnabled - && movement.IsMovingTo()) - { - Console.WriteLine("[autowalk-end] reason=interrupt"); - } movement.CancelMoveTo(WeenieError.ActionCancelled); }); movement.MakeMoveToManager(); motion.UnstickFromObject = physicsHost.PositionManager.UnStick; motion.InterruptCurrentMovement = () => { - if (PhysicsDiagnostics.ProbeAutoWalkEnabled - && movement.IsMovingTo()) - { - Console.WriteLine("[autowalk-end] reason=interrupt"); - } movement.CancelMoveTo(WeenieError.ActionCancelled); }; controller.PositionManager = physicsHost.PositionManager; diff --git a/src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs b/src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs index 394924ab..59f4338d 100644 --- a/src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs +++ b/src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs @@ -551,34 +551,6 @@ internal sealed class RuntimeRemotePhysicsUpdater if (!previousOnWalkable && finalOnWalkable) { - // Bug A investigation (2026-08-04, docs/ISSUES.md #32): - // capture the exact state HitGround is about to act on — - // see PhysicsDiagnostics.LogRemoteLanding for the field - // list and PhysicsDiagnostics.ProbeRemoteLandingEnabled - // for the discriminator table. TEMPORARY — strip once - // the live-test run has landed. - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeRemoteLandingEnabled) - { - AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLanding( - site: "per-tick", - guid: serverGuid, - airborneBefore: true, - gravitySet: rm.Body.HasGravity, - contact: rm.Body.InContact, - onWalkable: rm.Body.OnWalkable, - hasDefaultSink: rm.Motion.DefaultSink is not null, - resolveIsOnGround: resolveResult.IsOnGround, - sequencerStyle: sequencer?.CurrentStyle ?? 0, - sequencerMotion: sequencer?.CurrentMotion ?? 0); - if (!rm.Body.HasGravity) - { - AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLandingGateNoOp( - "per-tick", serverGuid); - } - AcDream.Core.Physics.PhysicsDiagnostics - .BeginRemoteLandingDispatchCapture(); - } - // #161: HitGround MUST run with the Gravity state bit // still set — CMotionInterp::HitGround (0x00528AC0) // gates on state & 0x400. Bug B deleted the clear that @@ -591,22 +563,6 @@ internal sealed class RuntimeRemotePhysicsUpdater // (MovementManager::HitGround 0x00524300). rm.Movement.HitGround(); - // Bug A investigation (2026-08-04) — the OUTCOME half of - // the probe above, emitted before the ownership re-check - // below can return so the two lines always pair. See - // PhysicsDiagnostics.LogRemoteLandingAfter and - // docs/research/2026-08-04-bug-a-h3-scheduler-diagnosis.md - // §6.1 for the three-way decision table. TEMPORARY. - if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeRemoteLandingEnabled) - { - AcDream.Core.Physics.PhysicsDiagnostics.LogRemoteLandingAfter( - site: "per-tick", - guid: serverGuid, - hitGroundInvoked: true, - sequencerStyle: sequencer?.CurrentStyle ?? 0, - sequencerMotion: sequencer?.CurrentMotion ?? 0, - forwardCommand: rm.Motion.InterpretedState.ForwardCommand); - } if (!IsCurrentOwner( record, rm, diff --git a/src/AcDream.Runtime/Physics/RuntimeSetPositionMoverPreparation.cs b/src/AcDream.Runtime/Physics/RuntimeSetPositionMoverPreparation.cs index 648e3d02..fc6aef6e 100644 --- a/src/AcDream.Runtime/Physics/RuntimeSetPositionMoverPreparation.cs +++ b/src/AcDream.Runtime/Physics/RuntimeSetPositionMoverPreparation.cs @@ -180,15 +180,6 @@ internal static class RuntimeSetPositionMoverPreparer float stepUp = setup is not null ? setup.StepUpHeight * scale : 0f; float stepDown = setup is not null ? setup.StepDownHeight * scale : 0f; - // #338 (TEMPORARY): first of three readings along the chain. Prints - // the raw authored pair alongside the scaled one, so a surprise here - // separates "wrong Setup" from "wrong scale" without a second run. - PhysicsDiagnostics.LogStepHeights( - "prepare", stepUp, stepDown, - setup is not null - ? $"authored=({setup.StepUpHeight:F3},{setup.StepDownHeight:F3}) scale={scale:F3}" - : "setup=NULL (retail dummy path, exact zero steps)"); - EntityCollisionFlags collisionFlags = EntityCollisionFlagsExt.FromPwdBitfield( record.Snapshot.ObjectDescriptionFlags ?? 0u); diff --git a/tests/AcDream.App.Tests/Diagnostics/LaunchOptionsDocumentationTests.cs b/tests/AcDream.App.Tests/Diagnostics/LaunchOptionsDocumentationTests.cs index 32f018dd..6297df33 100644 --- a/tests/AcDream.App.Tests/Diagnostics/LaunchOptionsDocumentationTests.cs +++ b/tests/AcDream.App.Tests/Diagnostics/LaunchOptionsDocumentationTests.cs @@ -51,8 +51,7 @@ public sealed class LaunchOptionsDocumentationTests ["src/AcDream.App/Rendering/Sky/SkyRenderer.cs"] = 1, ["src/AcDream.App/Rendering/TextureCache.cs"] = 1, ["src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs"] = 2, - ["src/AcDream.Core/Physics/PhysicsEngine.cs"] = 1, - ["src/AcDream.Core/Physics/TransitionTypes.cs"] = 3, + ["src/AcDream.Core/Physics/TransitionTypes.cs"] = 2, ["src/AcDream.Core/Vfx/PhysicsScriptRunner.cs"] = 1, ["src/AcDream.Core/World/SkyDescLoader.cs"] = 2, ["src/AcDream.Core.Net/GameEventWiring.cs"] = 1, diff --git a/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs b/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs index 5cb62a18..f00a019a 100644 --- a/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs +++ b/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs @@ -32,7 +32,6 @@ public sealed class RetailPViewPassExecutorTests "landscape-early", "terrain-clip", "clear-routing", - "outstage-routing", "landscape-late", "landscape-alpha", "indoor-routing", @@ -126,7 +125,6 @@ public sealed class RetailPViewPassExecutorTests Assert.Contains("entity-bucket", executor.Operations); Assert.Contains("cell-particles", executor.Operations); Assert.Contains("dynamics-particles", executor.Operations); - Assert.Contains("phantom-objects", executor.Operations); } [Fact] @@ -591,18 +589,6 @@ public sealed class RetailPViewPassExecutorTests ClipFrameAssembly clipAssembly, ClipViewSlice slice, int sliceIndex) => Operations.Add("clip-probe"); - public void EmitOutStageOwner( - WorldEntity entity, - Vector3 sphereCenter, - float sphereRadius, - int sliceIndex, - bool passed) => Operations.Add("outstage-owner"); - public void EmitOutStageRouting( - int sliceIndex, - IReadOnlyList entities, - ViewconeCuller viewcone) => Operations.Add("outstage-routing"); - public void EmitPhantomObjects(uint cellId, int survivorCount) => - Operations.Add("phantom-objects"); public void DrawLandscapeSlice( RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context) diff --git a/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs b/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs index 5c2b2a41..298d6c47 100644 --- a/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs +++ b/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs @@ -145,23 +145,6 @@ public sealed class WorldRenderDiagnosticsTests changed => Assert.StartsWith("[render-sig] frame=3 stable=1 branch=pview", changed)); } - [Fact] - public void OutStageParticleProbe_IsDisabledWithoutEnumerationAndSuppressesDuplicates() - { - var log = new RecordingLog(); - var diagnostics = new WorldRenderDiagnostics(new RecordingGlStateReader(), log); - var particles = new ParticleSystem(new EmitterDescRegistry()); - - diagnostics.EmitOutStageParticles(false, particles, new HashSet()); - diagnostics.EmitOutStageParticles(true, particles, new HashSet()); - diagnostics.EmitOutStageParticles(true, particles, new HashSet()); - - Assert.Single(log.Messages); - Assert.Equal( - "[outstage-pt] ids=0 attachedEmitters=0 matched=0 unattached=0 matchedIds=[] unmatchedIds=[]", - log.Messages[0]); - } - private static RenderGlStateSnapshot State(int depthFunction) => new( DepthTest: true, DepthWrite: true, diff --git a/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs b/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs index e00587f2..62b92bdf 100644 --- a/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs +++ b/tests/AcDream.App.Tests/Rendering/WorldRenderFrameBuilderTests.cs @@ -311,7 +311,6 @@ public sealed class WorldRenderFrameBuilderTests typeof(EnvCellRenderer), typeof(SceneLightingUbo), typeof(SceneLightingUboBinding), - typeof(RenderingDiagnostics), ]; string[] methodOrder = [ @@ -323,7 +322,6 @@ public sealed class WorldRenderFrameBuilderTests nameof(EnvCellRenderer.SetPointSnapshot), nameof(SceneLightingUbo.Build), nameof(SceneLightingUboBinding.Upload), - nameof(RenderingDiagnostics.EmitLight), ]; AssertCompiledCallOrder(calls, ownerOrder, methodOrder); diff --git a/tests/AcDream.Core.Tests/Physics/SupportProbeClassifierTests.cs b/tests/AcDream.Core.Tests/Physics/SupportProbeClassifierTests.cs deleted file mode 100644 index 742164b1..00000000 --- a/tests/AcDream.Core.Tests/Physics/SupportProbeClassifierTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Numerics; -using AcDream.Core.Physics; -using Xunit; - -namespace AcDream.Core.Tests.Physics; - -/// -/// #337 (2026-08-06 — TEMPORARY, delete with the [support] probe). -/// -/// -/// The [support] line's whole value is its support= verdict: -/// terrain, an object surface, or nothing. If that classifier is wrong, a -/// capture does not merely fail to answer — it answers CONFIDENTLY WRONG, and -/// this campaign has already spent two diagnoses on confident wrong answers. -/// These cover the decision boundaries directly, so the live capture can be -/// read at face value. -/// -/// -public sealed class SupportProbeClassifierTests -{ - private const float FlatNormalZ = 1f; - - [Fact] - public void NoContactPlane_IsUnsupported() - { - Assert.Equal( - "none", - PhysicsDiagnostics.ClassifySupport( - contactPlaneValid: false, - terrainSampled: true, - contactPlaneZAtXY: 100f, - contactPlaneNormalZ: FlatNormalZ, - terrainZ: 100f, - terrainNormalZ: FlatNormalZ)); - } - - [Fact] - public void PlaneAtTerrainHeightAndTilt_IsTerrain() - { - Assert.Equal( - "terrain", - PhysicsDiagnostics.ClassifySupport( - contactPlaneValid: true, - terrainSampled: true, - contactPlaneZAtXY: 41.25f, - contactPlaneNormalZ: 0.94f, - terrainZ: 41.26f, - terrainNormalZ: 0.94f)); - } - - [Fact] - public void PlaneWellAboveTerrain_IsObject() - { - // The rock-plateau shape: the body rests six metres above the ground. - Assert.Equal( - "object", - PhysicsDiagnostics.ClassifySupport( - contactPlaneValid: true, - terrainSampled: true, - contactPlaneZAtXY: 47.5f, - contactPlaneNormalZ: FlatNormalZ, - terrainZ: 41.5f, - terrainNormalZ: 0.9f)); - } - - [Fact] - public void SameHeightDifferentTilt_IsReportedSeparately() - { - // A collision surface lying flat against sloped ground. This must NOT - // collapse into either answer: it is precisely the ambiguous case, and - // guessing between them is what the probe exists to avoid. - Assert.Equal( - "coplanar-tilt-mismatch", - PhysicsDiagnostics.ClassifySupport( - contactPlaneValid: true, - terrainSampled: true, - contactPlaneZAtXY: 41.5f, - contactPlaneNormalZ: 1.0f, - terrainZ: 41.5f, - terrainNormalZ: 0.72f)); - } - - [Fact] - public void NoTerrainUnderTheBody_SaysSoRatherThanGuessing() - { - Assert.Equal( - "no-terrain", - PhysicsDiagnostics.ClassifySupport( - contactPlaneValid: true, - terrainSampled: false, - contactPlaneZAtXY: 12f, - contactPlaneNormalZ: FlatNormalZ, - terrainZ: float.NaN, - terrainNormalZ: float.NaN)); - } - - [Fact] - public void PlaneHeightIsEvaluatedAtTheBodysOwnXy() - { - // A 45-degree ramp through the origin: height must track X, or a body - // standing on a slope would read as displaced from its own support. - var slope = new Plane(Vector3.Normalize(new Vector3(-1f, 0f, 1f)), 0f); - - Assert.True(PhysicsDiagnostics.TryPlaneZAt(slope, 0f, 0f, out float atOrigin)); - Assert.Equal(0f, atOrigin, 3); - - Assert.True(PhysicsDiagnostics.TryPlaneZAt(slope, 10f, 0f, out float atTen)); - Assert.Equal(10f, atTen, 3); - } - - [Fact] - public void VerticalPlaneHasNoHeight() - { - // A wall is never a floor. Reporting a height for one would read as a - // wildly displaced surface and manufacture a false positive. - var wall = new Plane(new Vector3(1f, 0f, 0f), -5f); - - Assert.False(PhysicsDiagnostics.TryPlaneZAt(wall, 0f, 0f, out _)); - } -} diff --git a/tests/AcDream.Core.Tests/Physics/TransitFailProbeTests.cs b/tests/AcDream.Core.Tests/Physics/TransitFailProbeTests.cs index 99aaf6fa..c2caf4fc 100644 --- a/tests/AcDream.Core.Tests/Physics/TransitFailProbeTests.cs +++ b/tests/AcDream.Core.Tests/Physics/TransitFailProbeTests.cs @@ -137,12 +137,8 @@ public sealed class TransitFailProbeTests string log = sw.ToString(); - // The probe's own families must be completely silent on a healthy - // moving tick. (Console.Out may still carry unrelated one-shot - // process diagnostics — e.g. the #338 AnnounceStepHeightProbeOnce - // self-report, which fires unconditionally on the first IsPlayer - // resolve in the process regardless of any flag — so this checks - // the probe's own tag rather than asserting total silence.) + // The probe's own family must be completely silent on a healthy + // moving tick. Assert.DoesNotContain("[transit-fail", log); float actualDy = result.Position.Y - 0.00f; diff --git a/tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs b/tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs deleted file mode 100644 index 5581c11a..00000000 --- a/tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs +++ /dev/null @@ -1,119 +0,0 @@ -using AcDream.Core.Physics; -using DatReaderWriter.Enums; -using System.Collections.Generic; -using System.Numerics; -using Xunit; - -namespace AcDream.Core.Tests.Physics; - -/// -/// Tests for the ISSUES #83 H-disambiguation probe spike (spec -/// 2026-05-21-indoor-walk-miss-probe-design.md). -/// -/// Covers: -/// 1. PhysicsDiagnostics.ProbeWalkMissEnabled flag get/set roundtrip. -/// 2. WalkMissDiagnostic.AggregateNearestWalkable selects the nearest -/// walkable polygon by |dz| when the foot XY lies inside a poly's -/// local XY bounding box. -/// 3. WalkMissDiagnostic.AggregateNearestWalkable falls back to the -/// nearest poly by |dz| when no walkable poly XY-contains the foot, -/// reporting ContainsFootXY=false. -/// -public class WalkMissDiagnosticTests -{ - [Fact] - public void ProbeWalkMiss_StaticApi_Roundtrip() - { - bool initial = PhysicsDiagnostics.ProbeWalkMissEnabled; - try - { - PhysicsDiagnostics.ProbeWalkMissEnabled = true; - Assert.True(PhysicsDiagnostics.ProbeWalkMissEnabled); - - PhysicsDiagnostics.ProbeWalkMissEnabled = false; - Assert.False(PhysicsDiagnostics.ProbeWalkMissEnabled); - } - finally - { - PhysicsDiagnostics.ProbeWalkMissEnabled = initial; - } - } - - private static ResolvedPolygon MakeFloorPoly( - Vector3 v00, Vector3 v10, Vector3 v11, Vector3 v01) - { - var verts = new[] { v00, v10, v11, v01 }; - var normal = Vector3.Normalize(Vector3.Cross(v10 - v00, v01 - v00)); - float d = -Vector3.Dot(normal, v00); - return new ResolvedPolygon - { - Vertices = verts, - Plane = new System.Numerics.Plane(normal, d), - NumPoints = 4, - SidesType = CullMode.None, - }; - } - - /// - /// Foot at (0,0,1). Two walkable polys: a low one at Z=0 (foot is - /// 1 m above) and a high one at Z=0.8 (foot is 0.2 m above). - /// Aggregator picks the high one — smaller |dz|. - /// - [Fact] - public void AggregateNearestWalkable_PicksNearestByDz_WhenFootXYInsideMultiplePolys() - { - var lowFloor = MakeFloorPoly( - new Vector3(-5f, -5f, 0f), - new Vector3( 5f, -5f, 0f), - new Vector3( 5f, 5f, 0f), - new Vector3(-5f, 5f, 0f)); - var highFloor = MakeFloorPoly( - new Vector3(-2f, -2f, 0.8f), - new Vector3( 2f, -2f, 0.8f), - new Vector3( 2f, 2f, 0.8f), - new Vector3(-2f, 2f, 0.8f)); - - var resolved = new Dictionary - { - [1] = lowFloor, - [2] = highFloor, - }; - - var result = WalkMissDiagnostic.AggregateNearestWalkable( - resolved, - footLocal: new Vector3(0f, 0f, 1f), - floorZ: PhysicsGlobals.FloorZ); - - Assert.True(result.Found); - Assert.Equal((ushort)2, result.PolyId); - Assert.True(result.ContainsFootXY); - Assert.Equal(0.2f, result.Dz, precision: 5); - Assert.Equal(1.0f, result.NormalZ, precision: 5); - } - - /// - /// Foot at (10,10,1) — outside both poly XY bboxes. Aggregator - /// returns the poly with smallest |dz| but with ContainsFootXY=false. - /// - [Fact] - public void AggregateNearestWalkable_FallsBackByDz_WhenFootXYOutsideAllBboxes() - { - var poly = MakeFloorPoly( - new Vector3(-1f, -1f, 0.5f), - new Vector3( 1f, -1f, 0.5f), - new Vector3( 1f, 1f, 0.5f), - new Vector3(-1f, 1f, 0.5f)); - - var resolved = new Dictionary { [42] = poly }; - - var result = WalkMissDiagnostic.AggregateNearestWalkable( - resolved, - footLocal: new Vector3(10f, 10f, 1f), - floorZ: PhysicsGlobals.FloorZ); - - Assert.True(result.Found); - Assert.Equal((ushort)42, result.PolyId); - Assert.False(result.ContainsFootXY); - Assert.Equal(0.5f, result.Dz, precision: 5); - } -} diff --git a/tests/AcDream.Core.Tests/Rendering/RenderingDiagnosticsTests.cs b/tests/AcDream.Core.Tests/Rendering/RenderingDiagnosticsTests.cs index eaf0a5c2..628bc5bf 100644 --- a/tests/AcDream.Core.Tests/Rendering/RenderingDiagnosticsTests.cs +++ b/tests/AcDream.Core.Tests/Rendering/RenderingDiagnosticsTests.cs @@ -176,11 +176,4 @@ public sealed class RenderingDiagnosticsTests Assert.Single(set); Assert.Contains(0x020003F2u, set); } - - [Fact] - public void DumpEntitySourceIds_DefaultsEmpty_WhenEnvUnset() - { - // Env var is absent in the test host → probe inert. - Assert.Empty(RenderingDiagnostics.DumpEntitySourceIds); - } }