fix #435 (part 1): delete 17 probes that outlived their closed investigations
Each of these was temporary apparatus added to chase one bug, and each was supposed to be deleted in the commit that fixed it. Fourteen closed issues later they were still here: #337's support/wire-mesh trio, #171's sticky timeline, #119's viewer and entity dumps, #113's phantom probe, and a dozen more. 3,493 lines removed; the client now reads 144 environment variables instead of 161, and 47 temporary probes remain instead of 64. This is not only tidying. Every probe leaves a branch on its hot path when unset, several re-read the environment per call rather than caching, and the volume buries the diagnostics that are actually load-bearing. It is also a headless correctness matter: HeadlessStaticStateAudit reflects over PhysicsDiagnostics' flags to refuse a multi-session host when any is set, and cannot see probes that live outside that owner. Four files went entirely — WalkMissDiagnostic.cs, CollisionMeshWireframe.cs and two test files whose only subject was a deleted probe. TransitionTypes.SetContactPlane also sheds its CallerMemberName / CallerLineNumber parameters, which existed solely for #337's cpSrc= attribution and carried the instruction to strip them with the probe family; no call site passed them, so no behavior changes. F2's collision overlay survives and reverts to its proxy-cylinder form, which is what removing the ACDREAM_WIRE_MESH upgrade means. LaunchOptionsDocumentationTests earned its keep here: it refused the deletion until docs/launch-options.md moved the 17 rows into Retired and the frozen direct-read counts came down (PhysicsEngine.cs to zero, TransitionTypes.cs 3 to 2). The documentation could not drift during a cleanup this wide. The 14 probes that name no owning issue are deliberately NOT deleted. Nothing records when they became safe to remove, and guessing is how a future investigation loses apparatus it needed; #435 stays open for their attribution. Full hermetic suite 15,321 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
05bfe8d162
commit
0c5057c9ff
34 changed files with 57 additions and 3495 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,372 +0,0 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Rendering;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// #337 collision-mesh wireframe (2026-08-06 — TEMPORARY, strip with the #337
|
||||
/// probe family).
|
||||
///
|
||||
/// <para>
|
||||
/// 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.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This draws the actual geometry instead, in three colours that are meant to
|
||||
/// be read against each other:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Cyan</b> — 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.</item>
|
||||
/// <item><b>Magenta</b> — 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.</item>
|
||||
/// <item><b>Yellow</b> — 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.</item>
|
||||
/// </list>
|
||||
/// Dim orange keeps the old broadphase proxy visible so nothing the previous
|
||||
/// overlay showed has been taken away.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Geometry is resolved through the SAME prepared collision accessors the
|
||||
/// resolver queries (<c>PhysicsDataCache.GetFlatGfxObj</c> /
|
||||
/// <c>GetVisualBounds</c>) 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.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Pure reads. Nothing here mutates physics, registry, or render state; the
|
||||
/// caller owns the <see cref="DebugLineRenderer"/> frame.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private const int MaxLines = 40_000;
|
||||
|
||||
/// <summary>Per-object polygon ceiling, so one enormous formation cannot
|
||||
/// consume the whole budget and hide every other object near it.</summary>
|
||||
private const int MaxPolygonsPerObject = 4_000;
|
||||
|
||||
/// <summary>Half-width in metres of the terrain grid drawn under the
|
||||
/// player, and its sample spacing.</summary>
|
||||
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));
|
||||
|
||||
/// <summary>
|
||||
/// Emit the overlay for everything within
|
||||
/// <see cref="RenderingDiagnostics.CollisionMeshWireframeRadius"/> of
|
||||
/// <paramref name="centre"/>. Returns what it drew so the caller can
|
||||
/// report a capped frame rather than silently showing partial geometry.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<Vector3> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<Vector3> 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<int> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The one placement formula, matching the <c>[resolve-bldg]</c> probe's
|
||||
/// world transform for a shadow part
|
||||
/// (<c>TransitionTypes.FindObjCollisionsInCell</c>): scale in the part's
|
||||
/// own frame, then rotate, then translate to the registered position.
|
||||
/// </summary>
|
||||
private static Vector3 ToWorld(Vector3 local, in ShadowEntry shadow)
|
||||
=> shadow.Position + Vector3.Transform(local * shadow.Scale, shadow.Rotation);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>What one <see cref="CollisionMeshWireframe.Draw"/> emitted.
|
||||
/// <paramref name="ObjectsWithoutPhysicsGeometry"/> is the interesting one: a
|
||||
/// non-zero count means objects near the player carry no reachable collision
|
||||
/// polygons at all.</summary>
|
||||
internal readonly record struct CollisionMeshWireframeStats(
|
||||
int ObjectsConsidered,
|
||||
int PolygonsDrawn,
|
||||
int ObjectsWithoutPhysicsGeometry,
|
||||
int LinesDrawn,
|
||||
bool Capped);
|
||||
|
|
@ -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<WorldEntity> 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(
|
||||
|
|
|
|||
|
|
@ -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<WorldEntity> entities,
|
||||
ViewconeCuller viewcone);
|
||||
void EmitPhantomObjects(uint cellId, int survivorCount);
|
||||
void DrawLandscapeSlice(RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context);
|
||||
void DrawLandscapeSliceLate(RetailPViewFrameInput frame, RetailPViewLandscapeLateSliceContext context);
|
||||
void ClearInteriorDepth();
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -780,21 +780,6 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
private readonly HashSet<ulong> _missRequested = new();
|
||||
private readonly HashSet<ulong> _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<uint, (int MeshRefCount, int CacheBatches, int ZeroT, bool Culled)> _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<uint, int> _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
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #119 decisive probe: rate-limited <c>[dump-entity] WALK-REJECT</c> line
|
||||
/// for an <c>ACDREAM_DUMP_ENTITY</c>-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.
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #119 decisive probe: per-entity state dump at draw time for
|
||||
/// <c>ACDREAM_DUMP_ENTITY</c>-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).
|
||||
/// </summary>
|
||||
private void MaybeEmitEntityDump(
|
||||
in RenderInstanceCandidate entity,
|
||||
uint landblockId,
|
||||
bool culled,
|
||||
IReadOnlyList<RenderInstanceTuple> 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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// #105 apparatus: one <c>[tex-flush]</c> line on change of the staged-texture
|
||||
/// pending picture (plus a ~10 s heartbeat while anything is stuck). A healthy
|
||||
/// frame ends with <c>after=0</c>; <c>before==after>0</c> persisting at
|
||||
/// standstill is the white-walls mechanism live (staged uploads never applied).
|
||||
/// </summary>
|
||||
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})");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,15 +51,11 @@ internal sealed class WorldRenderDiagnostics
|
|||
{
|
||||
private readonly IRenderGlStateReader _gl;
|
||||
private readonly IRenderFrameDiagnosticLog _log;
|
||||
private readonly HashSet<uint> _lastViewerFloodCells = [];
|
||||
private readonly HashSet<uint> _outStageUnmatched = [];
|
||||
private readonly HashSet<uint> _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<uint, bool> _outStageOwnerVerdicts = [];
|
||||
private readonly Dictionary<uint, int> _phantomObjectSignatures = [];
|
||||
private string? _lastClipRouteSignature;
|
||||
private long _clipRouteSequence;
|
||||
private readonly List<uint> _clipRouteCellKeys = [];
|
||||
|
|
@ -258,75 +250,6 @@ internal sealed class WorldRenderDiagnostics
|
|||
_log.WriteLine($"[clip-route] n={_clipRouteSequence} {signature}");
|
||||
}
|
||||
|
||||
public void EmitOutStageOwner(
|
||||
bool enabled,
|
||||
IReadOnlySet<uint> 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<WorldEntity> 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<uint> 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<uint> 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<uint> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<uint> drawableCells)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #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.
|
||||
/// </summary>
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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<DatReaderWriter.DBObjs.GfxObj>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue