fix #435 (part 2, closes it): attribute the unowned probes — delete 7, reclassify 8, restore 1
Part 1 deleted probes whose owning issues were closed. These 14 named no issue at all, so each was traced to its introducing commit (git log -S) instead of guessed at. Attribution split them three ways: DELETED (7, investigations closed): ACDREAM_A8_DUMP_PV and ACDREAM_DUMP_LIVE_SPAWNS (Phase A8), ACDREAM_DUMP_CLOTHING (#37), ACDREAM_DUMP_EDGE_SLIDE (#32), ACDREAM_DUMP_STEPUP (L.2.3d-f), ACDREAM_DUMP_VENDOR (the vendor campaign, 25 call sites across 8 files), ACDREAM_DUMP_VITALS (#5, four independent read sites). VendorDiagnostics.cs went entirely. RECLASSIFIED (8, tools misfiled as probes): the DUMP_CELLS/DUMP_GFXOBJS fixture-extraction family (replay-harness tooling with a roundtrip test), PROBE_CELL (standing cell-transit tracer, pair of the permanent PROBE_RESOLVE), DUMP_SKY and HIDE_PART (generic isolation tools), and DUMP_STEEP_ROOF — which looked like an L.4 relic but observes LIVE divergence-register row AD-56; deleting it would have removed the only runtime lens on an active divergence. All moved to Permanent diagnostics with their attribution recorded. RESTORED (1): ACDREAM_DUMP_MOVE_TRUTH was deleted and un-deleted the same day. It is not a probe — the canonical nine-stop soak (run-connected-r6-soak.ps1) hard-fails every destination without its 'move-truth OUT' records, with a message that would misdirect the next operator. Under the no-workarounds rule the gate's mechanism is restored, not left broken with an IOU (#437, closed). Process lesson recorded on both issues: a closed owning issue is NOT sufficient to delete a probe — grep tools/ and the contract tests for consumers first. Also lands the owner-requested default-off invariant: every diagnostic in the codebase is inert until its env var is explicitly set. Exactly four flags default ON and none is a diagnostic — RETAIL_CHASE, CAMERA_COLLIDE, CAMERA_ALIGN_SLOPE, RETAIL_CLOSE_DEGRADES are retail behaviors wearing an A/B off-switch. That set is now FROZEN by LaunchOptionsDocumentationTests.OnlyTheFourRetailBehaviorFlagsDefaultOn; docs/launch-options.md's Conventions and CLAUDE.md state the rule, and CLAUDE.md now binds future probes to a documented row in the same commit. The client reads 137 environment variables (161 at audit start); 40 temporary probes remain, every one attributed. Full hermetic suite 15,322 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0c5057c9ff
commit
c1e6e3da44
26 changed files with 256 additions and 693 deletions
|
|
@ -562,8 +562,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
d.EntityObjects,
|
||||
teardown,
|
||||
d.PlayerIdentity,
|
||||
dormantLiveEntities,
|
||||
d.Options.DumpLiveSpawns ? d.Log : null);
|
||||
dormantLiveEntities);
|
||||
// 2026-08-08 vendor-approach root cause: the local player's
|
||||
// publication-chain host resolves moveto/sticky targets through
|
||||
// RuntimePhysicsState.ResolveObjectTableHost. Bind the graphical
|
||||
|
|
@ -709,7 +708,6 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
d.PlayerIdentity,
|
||||
deletion,
|
||||
dormantLiveEntities,
|
||||
d.Options.DumpLiveSpawns ? d.Log : null,
|
||||
firstEntryDrive,
|
||||
acceptedPositionDrive);
|
||||
bindings.Adopt(
|
||||
|
|
|
|||
|
|
@ -61,14 +61,6 @@ internal sealed class PlayerInteractionMovementSink(
|
|||
Height = approach.TargetHeight,
|
||||
};
|
||||
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] BeginApproach guid=0x{approach.Target.ServerGuid:X8} "
|
||||
+ $"movementType={movement.Type} distanceToObject={parameters.DistanceToObject} "
|
||||
+ $"canCharge={parameters.CanCharge} target=0x{movement.ObjectId:X8}");
|
||||
}
|
||||
|
||||
// PerformMovement cancels at its head. Do it explicitly before the
|
||||
// intent is armed so cancellation of the preceding move cannot clear
|
||||
// the new request; the internal second call is then a retail no-op.
|
||||
|
|
|
|||
|
|
@ -181,11 +181,6 @@ internal sealed class SelectionInteractionController
|
|||
// ships with it.
|
||||
if (useImmediately && !_query.IsWieldedByPlayer(guid))
|
||||
{
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] route=dblclick-world PickAndStoreSelection guid=0x{guid:X8} enqueue=Activate");
|
||||
}
|
||||
EnqueueIdentityBound(
|
||||
RuntimeQueuedInteractionKind.Activate,
|
||||
guid,
|
||||
|
|
@ -221,11 +216,6 @@ internal sealed class SelectionInteractionController
|
|||
_toast?.Invoke("Nothing selected");
|
||||
return;
|
||||
}
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] route=keyboard-use UseCurrentSelection guid=0x{selected:X8} enqueue=Use");
|
||||
}
|
||||
EnqueueIdentityBound(
|
||||
RuntimeQueuedInteractionKind.Use,
|
||||
selected,
|
||||
|
|
@ -292,22 +282,10 @@ internal sealed class SelectionInteractionController
|
|||
bool ownedByPlayer = _items.IsOwnedByPlayer(serverGuid);
|
||||
bool useable = ownedByPlayer || _query.IsUseable(serverGuid);
|
||||
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] RequestUse entry guid=0x{serverGuid:X8} ownedByPlayer={ownedByPlayer} useable={useable}");
|
||||
}
|
||||
|
||||
if (useable
|
||||
&& _query.TryGetApproach(serverGuid, out InteractionApproach approach)
|
||||
&& !approach.IsCloseRange)
|
||||
{
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] RequestUse guid=0x{serverGuid:X8} branch=approach-armed useRadius={approach.UseRadius} isCloseRange={approach.IsCloseRange}");
|
||||
}
|
||||
|
||||
// Genuinely out of range (a real walk, not just a turn) —
|
||||
// mirror SendPickup's arrival-gated shape: arm the transaction
|
||||
// on the approach token BEFORE the movement starts (so a
|
||||
|
|
@ -327,17 +305,7 @@ internal sealed class SelectionInteractionController
|
|||
token.ControllerLifetime,
|
||||
token.ApproachGeneration),
|
||||
out _);
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] TryArmPostArrivalUse guid=0x{serverGuid:X8} armed={armed} approachToken=({token.ControllerLifetime},{token.ApproachGeneration})");
|
||||
}
|
||||
});
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] BeginApproach-result guid=0x{serverGuid:X8} started={started} armed={armed} stopDistance={approach.UseRadius} target=0x{approach.Target.ServerGuid:X8}");
|
||||
}
|
||||
if (!started || !armed)
|
||||
{
|
||||
// Release whatever got captured (or the caller's own
|
||||
|
|
@ -360,11 +328,6 @@ internal sealed class SelectionInteractionController
|
|||
// — keep retail's immediate send; ACE's own "already within use
|
||||
// distance" branch (Player_Move.cs:65-87) calls back synchronously,
|
||||
// so there is no arrival gap to race here.
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] RequestUse guid=0x{serverGuid:X8} branch=immediate-dispatch ownedByPlayer={ownedByPlayer} useable={useable}");
|
||||
}
|
||||
RuntimeInteractionDispatchResult result =
|
||||
_transactions.TryDispatchUse(
|
||||
serverGuid,
|
||||
|
|
@ -373,11 +336,6 @@ internal sealed class SelectionInteractionController
|
|||
reservation,
|
||||
_transport,
|
||||
out uint sequence);
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] RequestUse guid=0x{serverGuid:X8} TryDispatchUse verdict={result} seq={sequence}");
|
||||
}
|
||||
if (result == RuntimeInteractionDispatchResult.NotInWorld)
|
||||
_toast?.Invoke("Not in world");
|
||||
if (result == RuntimeInteractionDispatchResult.Dispatched)
|
||||
|
|
@ -609,22 +567,6 @@ internal sealed class SelectionInteractionController
|
|||
RuntimePendingUse pending,
|
||||
bool accepted)
|
||||
{
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
// Diagnostic-only re-query — TryGetApproach is a pure read with
|
||||
// no side effects, so an extra call here (gated off in
|
||||
// production) cannot change RequestUse's own dispatch outcome.
|
||||
string distanceText = "n/a";
|
||||
if (_query.TryGetApproach(pending.ServerGuid, out InteractionApproach diagApproach))
|
||||
{
|
||||
float dx = diagApproach.Target.Entity.Position.X - diagApproach.Player.Position.X;
|
||||
float dy = diagApproach.Target.Entity.Position.Y - diagApproach.Player.Position.Y;
|
||||
distanceText = MathF.Sqrt(dx * dx + dy * dy).ToString("F2");
|
||||
}
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] HandleUseApproachCompletion guid=0x{pending.ServerGuid:X8} accepted={accepted} playerToTargetDist={distanceText}");
|
||||
}
|
||||
|
||||
if (!accepted)
|
||||
{
|
||||
pending.Reservation?.CancelBeforeDispatch();
|
||||
|
|
|
|||
|
|
@ -571,12 +571,6 @@ internal sealed class WorldSelectionQuery
|
|||
bool haveSpawn = _liveEntities.TryGetSnapshot(serverGuid, out var spawn);
|
||||
bool fromWire = haveSpawn && spawn.UseRadius is > 0f;
|
||||
float radius = fromWire ? spawn.UseRadius!.Value : DefaultUseRadius;
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] GetUseRadius guid=0x{serverGuid:X8} radius={radius} "
|
||||
+ $"source={(fromWire ? "wire" : "fallback-0.6")}");
|
||||
}
|
||||
return radius;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,17 +58,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
/// </summary>
|
||||
private readonly RuntimeWorldTransitState _transit;
|
||||
|
||||
private int _received;
|
||||
private int _hydrated;
|
||||
private int _noPosition;
|
||||
private int _noSetup;
|
||||
private int _missingSetup;
|
||||
private int _noMesh;
|
||||
private int _noCycle;
|
||||
private int _zeroFramerate;
|
||||
private int _singleFrame;
|
||||
private int _missingPartFrames;
|
||||
|
||||
public DatLiveEntityProjectionMaterializer(
|
||||
RuntimeOptions options,
|
||||
IDatReaderWriter dats,
|
||||
|
|
@ -116,16 +105,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
|
||||
public void ResetSessionState()
|
||||
{
|
||||
_received = 0;
|
||||
_hydrated = 0;
|
||||
_noPosition = 0;
|
||||
_noSetup = 0;
|
||||
_missingSetup = 0;
|
||||
_noMesh = 0;
|
||||
_noCycle = 0;
|
||||
_zeroFramerate = 0;
|
||||
_singleFrame = 0;
|
||||
_missingPartFrames = 0;
|
||||
}
|
||||
|
||||
public bool TryMaterialize(
|
||||
|
|
@ -157,18 +136,10 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
if (appearanceUpdate is not null && expectedRecord is null)
|
||||
return false;
|
||||
|
||||
_received++;
|
||||
bool dumpLiveSpawns = _options.DumpLiveSpawns;
|
||||
DumpSpawn(canonicalSpawn, dumpLiveSpawns);
|
||||
|
||||
if (!_origin.IsKnown)
|
||||
return false;
|
||||
if (canonicalSpawn.Position is null || canonicalSpawn.SetupTableId is null)
|
||||
{
|
||||
if (canonicalSpawn.Position is null)
|
||||
_noPosition++;
|
||||
else
|
||||
_noSetup++;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -216,13 +187,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
_collisionAssets.CacheSetup(canonicalSpawn.SetupTableId.Value, setup);
|
||||
if (setup is null)
|
||||
{
|
||||
_missingSetup++;
|
||||
if (dumpLiveSpawns)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"live: DROP setup dat 0x{canonicalSpawn.SetupTableId.Value:X8} missing "
|
||||
+ $"(guid=0x{canonicalSpawn.Guid:X8})");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -248,14 +212,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
List<MeshRef> flattened = [.. SetupMesh.Flatten(setup, idleFrame)];
|
||||
IReadOnlyList<CreateObject.AnimPartChange> animPartChanges =
|
||||
canonicalSpawn.AnimPartChanges ?? Array.Empty<CreateObject.AnimPartChange>();
|
||||
bool dumpClothing = _options.DumpClothing && setup.Parts.Count >= 10;
|
||||
DumpClothingHeader(
|
||||
canonicalSpawn,
|
||||
setup,
|
||||
flattened,
|
||||
idleFrame,
|
||||
animPartChanges,
|
||||
dumpClothing);
|
||||
|
||||
foreach (CreateObject.AnimPartChange change in animPartChanges)
|
||||
{
|
||||
|
|
@ -273,17 +229,14 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
baseId => ResolveCollisionPart(baseId));
|
||||
|
||||
if (_options.RetailCloseDegrades && IsIssue47HumanoidSetup(setup))
|
||||
ApplyRetailCloseDegrades(flattened, dumpClothing);
|
||||
ApplyRetailCloseDegrades(flattened);
|
||||
|
||||
IReadOnlyList<CreateObject.TextureChange> textureChanges =
|
||||
canonicalSpawn.TextureChanges ?? Array.Empty<CreateObject.TextureChange>();
|
||||
Dictionary<int, Dictionary<uint, uint>>? surfaceOverrides =
|
||||
ResolveSurfaceOverrides(
|
||||
canonicalSpawn,
|
||||
flattened,
|
||||
textureChanges,
|
||||
dumpClothing,
|
||||
dumpLiveSpawns);
|
||||
textureChanges);
|
||||
|
||||
float scale = canonicalSpawn.ObjScale ?? 1f;
|
||||
Matrix4x4 scaleMatrix = Matrix4x4.CreateScale(scale);
|
||||
|
|
@ -294,7 +247,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
var indexedPartAvailable = new bool[flattened.Count];
|
||||
var animatedPartTemplate = new LiveAnimationPartTemplate[flattened.Count];
|
||||
var bounds = new LocalBoundsAccumulator();
|
||||
int clothingTriangles = 0;
|
||||
|
||||
for (int partIndex = 0; partIndex < flattened.Count; partIndex++)
|
||||
{
|
||||
|
|
@ -318,23 +270,10 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
drawable);
|
||||
if (gfx is null)
|
||||
{
|
||||
if (dumpClothing)
|
||||
Console.WriteLine($" EMIT part={partIndex:D2} gfx=0x{part.GfxObjId:X8} GFXOBJ_DAT_MISSING -> 0 tris");
|
||||
continue;
|
||||
}
|
||||
|
||||
_collisionAssets.CacheGfxObj(part.GfxObjId, gfx);
|
||||
if (dumpClothing)
|
||||
{
|
||||
var subMeshes = GfxObjMesh.Build(gfx, _dats);
|
||||
int triangles = 0;
|
||||
foreach (var subMesh in subMeshes)
|
||||
triangles += subMesh.Indices.Length / 3;
|
||||
clothingTriangles += triangles;
|
||||
Console.WriteLine(
|
||||
$" EMIT part={partIndex:D2} gfx=0x{part.GfxObjId:X8} "
|
||||
+ $"subMeshes={subMeshes.Count} tris={triangles}");
|
||||
}
|
||||
|
||||
if (GfxObjBounds.Get(gfx) is { } partBounds)
|
||||
bounds.Add(transform, partBounds);
|
||||
|
|
@ -346,21 +285,8 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
|
||||
if (meshRefs.Count == 0)
|
||||
{
|
||||
_noMesh++;
|
||||
if (dumpLiveSpawns)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"live: DROP no mesh refs from setup 0x{canonicalSpawn.SetupTableId.Value:X8} "
|
||||
+ $"(guid=0x{canonicalSpawn.Guid:X8})");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (dumpClothing)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$" TOTAL tris={clothingTriangles} meshRefs={meshRefs.Count} "
|
||||
+ $"(parts.Count={flattened.Count})");
|
||||
}
|
||||
|
||||
PaletteOverride? paletteOverride = CreatePaletteOverride(canonicalSpawn);
|
||||
PartOverride[] partOverrides = CreatePartOverrides(animPartChanges);
|
||||
|
|
@ -456,7 +382,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
indexedPartAvailable,
|
||||
animatedPartTemplate,
|
||||
bounds,
|
||||
dumpLiveSpawns,
|
||||
expectedCreateIntegrationVersion,
|
||||
synchronizeAnimation: supersessionRecovery);
|
||||
}
|
||||
|
|
@ -477,9 +402,7 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
return slotZeroId;
|
||||
}
|
||||
|
||||
private void ApplyRetailCloseDegrades(
|
||||
List<MeshRef> parts,
|
||||
bool dumpClothing)
|
||||
private void ApplyRetailCloseDegrades(List<MeshRef> parts)
|
||||
{
|
||||
for (int partIndex = 0; partIndex < parts.Count; partIndex++)
|
||||
{
|
||||
|
|
@ -495,33 +418,13 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
}
|
||||
|
||||
parts[partIndex] = new MeshRef(resolvedId, part.PartTransform);
|
||||
if (dumpClothing)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$" DEGRADE part={partIndex:D2} gfx=0x{part.GfxObjId:X8} "
|
||||
+ $"-> close=0x{resolvedId:X8}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<int, Dictionary<uint, uint>>? ResolveSurfaceOverrides(
|
||||
WorldSession.EntitySpawn spawn,
|
||||
IReadOnlyList<MeshRef> parts,
|
||||
IReadOnlyList<CreateObject.TextureChange> textureChanges,
|
||||
bool dumpClothing,
|
||||
bool dumpLiveSpawns)
|
||||
IReadOnlyList<CreateObject.TextureChange> textureChanges)
|
||||
{
|
||||
if (dumpClothing)
|
||||
{
|
||||
Console.WriteLine($" TextureChanges count={textureChanges.Count}");
|
||||
foreach (CreateObject.TextureChange change in textureChanges)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$" TC part={change.PartIndex:D2} oldTex=0x{change.OldTexture:X8} "
|
||||
+ $"-> newTex=0x{change.NewTexture:X8}");
|
||||
}
|
||||
}
|
||||
|
||||
if (textureChanges.Count == 0)
|
||||
return null;
|
||||
|
||||
|
|
@ -536,8 +439,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
oldToNew[change.OldTexture] = change.NewTexture;
|
||||
}
|
||||
|
||||
bool statueDiagnostic = dumpLiveSpawns
|
||||
&& spawn.Name?.Contains("Statue", StringComparison.OrdinalIgnoreCase) == true;
|
||||
var result = new Dictionary<int, Dictionary<uint, uint>>();
|
||||
for (int partIndex = 0; partIndex < parts.Count; partIndex++)
|
||||
{
|
||||
|
|
@ -547,12 +448,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
GfxObj? gfx = _dats.Get<GfxObj>(parts[partIndex].GfxObjId);
|
||||
if (gfx is null)
|
||||
{
|
||||
if (statueDiagnostic)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"live: [STATUE] resolve part={partIndex} "
|
||||
+ $"GfxObj 0x{parts[partIndex].GfxObjId:X8} missing");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_collisionAssets.CacheGfxObj(parts[partIndex].GfxObjId, gfx);
|
||||
|
|
@ -729,7 +624,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
IReadOnlyList<bool> indexedPartAvailable,
|
||||
IReadOnlyList<LiveAnimationPartTemplate> animatedPartTemplate,
|
||||
LocalBoundsAccumulator bounds,
|
||||
bool dumpLiveSpawns,
|
||||
ulong expectedCreateIntegrationVersion,
|
||||
bool synchronizeAnimation)
|
||||
{
|
||||
|
|
@ -835,7 +729,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
_worldEvents.UpsertCurrent(snapshot);
|
||||
if (_runtime.TryMarkWorldSpawnPublished(spawn.Guid))
|
||||
_worldEvents.FireEntitySpawned(snapshot);
|
||||
_hydrated++;
|
||||
|
||||
if (!_runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
|
|
@ -921,18 +814,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
return false;
|
||||
}
|
||||
|
||||
if (dumpLiveSpawns && _received % 20 == 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"live: animated={_runtime.AnimationRuntimeCount} "
|
||||
+ $"animReject: noCycle={_noCycle} fr0={_zeroFramerate} "
|
||||
+ $"1frame={_singleFrame} partFrames={_missingPartFrames}");
|
||||
Console.WriteLine(
|
||||
$"live: summary recv={_received} hydrated={_hydrated} "
|
||||
+ $"drops: noPos={_noPosition} noSetup={_noSetup} "
|
||||
+ $"setupMissing={_missingSetup} noMesh={_noMesh}");
|
||||
}
|
||||
|
||||
return _runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion);
|
||||
|
|
@ -961,18 +842,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
spawn,
|
||||
idleCycle);
|
||||
}
|
||||
if (!retainedAnimation)
|
||||
{
|
||||
if (idleCycle is null)
|
||||
_noCycle++;
|
||||
else if (idleCycle.Framerate == 0f)
|
||||
_zeroFramerate++;
|
||||
else if (idleCycle.HighFrame <= idleCycle.LowFrame)
|
||||
_singleFrame++;
|
||||
else if (idleCycle.Animation.PartFrames.Count <= 1)
|
||||
_missingPartFrames++;
|
||||
}
|
||||
|
||||
if (!retainedAnimation
|
||||
&& idleCycle is not null
|
||||
&& idleCycle.Framerate != 0f
|
||||
|
|
@ -1159,56 +1028,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
|||
: null;
|
||||
}
|
||||
|
||||
private void DumpSpawn(WorldSession.EntitySpawn spawn, bool enabled)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
string position = spawn.Position is { } p
|
||||
? $"({p.PositionX:F1},{p.PositionY:F1},{p.PositionZ:F1})@0x{p.LandblockId:X8}"
|
||||
: "no-pos";
|
||||
string setup = spawn.SetupTableId is { } setupId
|
||||
? $"0x{setupId:X8}"
|
||||
: "no-setup";
|
||||
string physicsTable = spawn.Physics?.PhysicsScriptTableId is { } tableId
|
||||
? $"0x{tableId:X8}"
|
||||
: "no-petable";
|
||||
string name = spawn.Name is { Length: > 0 } foundName
|
||||
? $"\"{foundName}\""
|
||||
: "no-name";
|
||||
string itemType = spawn.ItemType is { } foundItemType
|
||||
? $"0x{foundItemType:X8}"
|
||||
: "no-itemtype";
|
||||
Console.WriteLine(
|
||||
$"live: spawn guid=0x{spawn.Guid:X8} name={name} setup={setup} pos={position} "
|
||||
+ $"petable={physicsTable} itemType={itemType} "
|
||||
+ $"animParts={spawn.AnimPartChanges?.Count ?? 0} "
|
||||
+ $"texChanges={spawn.TextureChanges?.Count ?? 0} "
|
||||
+ $"subPalettes={spawn.SubPalettes?.Count ?? 0}");
|
||||
}
|
||||
|
||||
private void DumpClothingHeader(
|
||||
WorldSession.EntitySpawn spawn,
|
||||
Setup setup,
|
||||
IReadOnlyList<MeshRef> flattened,
|
||||
AnimationFrame? idleFrame,
|
||||
IReadOnlyList<CreateObject.AnimPartChange> changes,
|
||||
bool enabled)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
Console.WriteLine(
|
||||
$"\n=== DUMP_CLOTHING: guid=0x{spawn.Guid:X8} name='{spawn.Name}' "
|
||||
+ $"setup=0x{setup.Id:X8} setup.Parts.Count={setup.Parts.Count} "
|
||||
+ $"flatten.Count={flattened.Count} APC={changes.Count} ===");
|
||||
foreach (CreateObject.AnimPartChange change in changes)
|
||||
Console.WriteLine($" APC part={change.PartIndex:D2} -> gfx=0x{change.NewModelId:X8}");
|
||||
Console.WriteLine(
|
||||
$" basePalette=0x{spawn.BasePaletteId ?? 0:X8} "
|
||||
+ $"subPalettes={spawn.SubPalettes?.Count ?? 0}");
|
||||
}
|
||||
|
||||
private static bool IsIssue47HumanoidSetup(Setup setup)
|
||||
{
|
||||
if (setup.Parts.Count != 34)
|
||||
|
|
|
|||
|
|
@ -265,12 +265,6 @@ public static class PortalVisibilityBuilder
|
|||
// stands near a doorway plane).
|
||||
private const float SeedInPlaneEpsilon = 0.0002f;
|
||||
|
||||
// TEMP diagnostic (Phase A8.F visual-gate triage; strip after): ACDREAM_A8_DUMP_PV=1 dumps the
|
||||
// local→NDC→clipped portal geometry for the first 2 Build calls per distinct camera cell.
|
||||
private static readonly bool s_pvDump =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_A8_DUMP_PV") == "1";
|
||||
private static readonly Dictionary<uint, int> s_pvDumpCount = new();
|
||||
|
||||
/// <summary>
|
||||
/// #120 observable: total convergence-tripwire firings across both the
|
||||
/// interior <see cref="Build"/> and the exterior look-in propagation.
|
||||
|
|
@ -386,36 +380,6 @@ public static class PortalVisibilityBuilder
|
|||
int churnReenqueues = 0;
|
||||
var churnReciprocal = churnProbe ? new System.Text.StringBuilder(256) : null;
|
||||
|
||||
bool pvDump = false;
|
||||
if (s_pvDump)
|
||||
{
|
||||
lock (s_pvDumpCount)
|
||||
{
|
||||
s_pvDumpCount.TryGetValue(cameraCell.CellId, out int dc);
|
||||
if (dc < 2) { s_pvDumpCount[cameraCell.CellId] = dc + 1; pvDump = true; }
|
||||
}
|
||||
if (pvDump)
|
||||
{
|
||||
Console.WriteLine($"[pv-dump] camCell=0x{cameraCell.CellId:X8} portals={cameraCell.Portals.Count} polyLists={cameraCell.PortalPolygons.Count} vp[M11={viewProj.M11:F3} M22={viewProj.M22:F3} M33={viewProj.M33:F3} M34={viewProj.M34:F3} M43={viewProj.M43:F3} M44={viewProj.M44:F3}]");
|
||||
// Camera-cell portal census (A8.F triage 2026-05-29): report, for EVERY
|
||||
// portal, the exact inputs the BFS guards read — BEFORE the guards run, so
|
||||
// a portal the loop silently `continue`s past is still visible here. An
|
||||
// empty OUTSIDEVIEW can then be traced to the precise gate: polyLen<3 (empty
|
||||
// polygon from EnvCellLandblockBuildBuilder), interiorSide=false (camera back-facing the
|
||||
// portal — a legitimately-empty result, not a bug), or (if both OK) a
|
||||
// downstream projection/clip failure shown by the EXIT-PROJ/EXIT-CLIP lines.
|
||||
for (int ci = 0; ci < cameraCell.Portals.Count; ci++)
|
||||
{
|
||||
int plen = ci < cameraCell.PortalPolygons.Count
|
||||
? (cameraCell.PortalPolygons[ci]?.Length ?? -1) : -2;
|
||||
bool hasPlane = ci < cameraCell.ClipPlanes.Count;
|
||||
bool interiorSide = !hasPlane || CameraOnInteriorSide(cameraCell, ci, cameraPos);
|
||||
var n = hasPlane ? cameraCell.ClipPlanes[ci].Normal : Vector3.Zero;
|
||||
Console.WriteLine($"[pv-dump] CAMPORTAL[{ci}] other=0x{cameraCell.Portals[ci].OtherCellId:X4} polyLen={plen} hasPlane={hasPlane} interiorSide={interiorSide} planeN=({n.X:F3},{n.Y:F3},{n.Z:F3})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// T2 (BR-4): retail's growth propagation is IN PLACE, never by re-enqueue
|
||||
// — PView::AddViewToPortals (Ghidra 0x005a52d0, pc:433446): first
|
||||
// discovery enqueues via InsCellTodoList; growth into a cell whose
|
||||
|
|
@ -481,8 +445,6 @@ public static class PortalVisibilityBuilder
|
|||
continue;
|
||||
}
|
||||
|
||||
bool dx = pvDump && cell.Portals[i].OtherCellId == 0xFFFF;
|
||||
|
||||
// (R-A2b Phase 1 pin, throwaway) Log the side-test inputs for EVERY portal so a back-portal
|
||||
// traversal (cell=0x..0173 p->0x0171) can be attributed to the side test.
|
||||
// Strip with the rest of the [pv-trace] apparatus.
|
||||
|
|
@ -505,7 +467,6 @@ public static class PortalVisibilityBuilder
|
|||
&& !CameraOnInteriorSide(cell, i, cameraPos))
|
||||
{
|
||||
trace?.Add($"portal cell=0x{cell.CellId:X8} p{i}->0x{portal.OtherCellId:X4} skip=side");
|
||||
if (dx) Console.WriteLine($"[pv-dump] EXIT-CULLED(side) cell=0x{cell.CellId:X8} p{i} localN={poly.Length} hasClipPlane={(i < cell.ClipPlanes.Count)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -526,8 +487,6 @@ public static class PortalVisibilityBuilder
|
|||
endCount - processedCount,
|
||||
clippedRegion,
|
||||
out int clipVerts);
|
||||
if (dx) Console.WriteLine($"[pv-dump] EXIT-PROJ cell=0x{cell.CellId:X8} p{i} localN={poly.Length} clipN={clipVerts} local0=({poly[0].X:F2},{poly[0].Y:F2},{poly[0].Z:F2})");
|
||||
if (dx) Console.WriteLine($"[pv-dump] EXIT-CLIP cell=0x{cell.CellId:X8} p{i} currentViewPolys={currentView.Polygons.Count} clipResult={clippedRegion.Count}");
|
||||
|
||||
// Empty clip = no flood through this portal, period — retail's empty-GetClip rule
|
||||
// (polyClipFinish <3 survivors → reject; ClipPortals adds no view). The
|
||||
|
|
@ -544,13 +503,6 @@ public static class PortalVisibilityBuilder
|
|||
|
||||
if (portal.OtherCellId == 0xFFFF)
|
||||
{
|
||||
if (pvDump)
|
||||
{
|
||||
Console.WriteLine($"[pv-dump] EXIT cell=0x{cell.CellId:X8} p{i} localN={poly.Length} clipVerts={clipVerts} clipPolys={clippedRegion.Count}");
|
||||
Console.WriteLine($"[pv-dump] local=[{string.Join(" ", System.Array.ConvertAll(poly, v => $"({v.X:F2},{v.Y:F2},{v.Z:F2})"))}]");
|
||||
foreach (var cp in clippedRegion)
|
||||
Console.WriteLine($"[pv-dump] clipped({cp.Vertices.Length})=[{string.Join(" ", System.Array.ConvertAll((Vector2[])cp.Vertices, v => $"({v.X:F3},{v.Y:F3})"))}]");
|
||||
}
|
||||
// Exit portal -> outdoors visible through this (clipped) opening.
|
||||
// OutsideView gates DRAWN color (terrain/sky/scissor), and the
|
||||
// shell that rasterizes this aperture draws +drawLiftZ above
|
||||
|
|
@ -688,9 +640,6 @@ public static class PortalVisibilityBuilder
|
|||
ProcessCellPortals(cell, 0);
|
||||
}
|
||||
|
||||
if (pvDump)
|
||||
Console.WriteLine($"[pv-dump] OUTSIDEVIEW polys={frame.OutsideView.Polygons.Count} bfsCellViews={frame.CellViews.Count} crossBldg={frame.CrossBuildingViews.Count}");
|
||||
|
||||
// Phase U.4c flap probe (ACDREAM_PROBE_FLAP) — read-only per-frame snapshot of the
|
||||
// root cell's per-portal side-test + projection + the frame's exit/visible counts.
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled)
|
||||
|
|
|
|||
|
|
@ -42,14 +42,16 @@ public sealed record RuntimeOptions(
|
|||
string? LivePass,
|
||||
bool DevTools,
|
||||
bool UncappedRendering,
|
||||
/// <summary>#435 kept deliberately: NOT a spent probe. The canonical
|
||||
/// nine-stop soak (`tools/run-connected-r6-soak.ps1`) hard-gates on the
|
||||
/// `move-truth OUT` records this emits — it is the automated proof that
|
||||
/// production input produced outbound movement traffic.</summary>
|
||||
bool DumpMoveTruth,
|
||||
bool DumpSky,
|
||||
bool NoAudio,
|
||||
int HidePartIndex,
|
||||
bool RetailCloseDegrades,
|
||||
bool DumpSceneryZ,
|
||||
bool DumpLiveSpawns,
|
||||
bool DumpClothing,
|
||||
int? LegacyStreamRadius,
|
||||
bool RetailUi,
|
||||
/// <summary>Campaign CC slice CC4: interim env/test-only seam that opens
|
||||
|
|
@ -157,8 +159,6 @@ public sealed record RuntimeOptions(
|
|||
// only for before/after diagnostic comparisons.
|
||||
RetailCloseDegrades: !string.Equals(env("ACDREAM_RETAIL_CLOSE_DEGRADES"), "0", StringComparison.Ordinal),
|
||||
DumpSceneryZ: IsExactlyOne(env("ACDREAM_DUMP_SCENERY_Z")),
|
||||
DumpLiveSpawns: IsExactlyOne(env("ACDREAM_DUMP_LIVE_SPAWNS")),
|
||||
DumpClothing: IsExactlyOne(env("ACDREAM_DUMP_CLOTHING")),
|
||||
// Legacy override for ACDREAM_STREAM_RADIUS. Caller applies it on
|
||||
// top of the quality preset's radii. Null when unset or invalid.
|
||||
LegacyStreamRadius: TryParseNonNegativeInt(env("ACDREAM_STREAM_RADIUS")),
|
||||
|
|
|
|||
|
|
@ -525,11 +525,6 @@ public sealed class ItemInteractionController : IDisposable
|
|||
switch (mode)
|
||||
{
|
||||
case InteractionModeKind.Use:
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] route=target-mode-click OfferPrimaryClick(Use) guid=0x{targetGuid:X8}");
|
||||
}
|
||||
ClearTargetMode();
|
||||
accepted = ActivateItem(targetGuid);
|
||||
break;
|
||||
|
|
@ -562,11 +557,6 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// </summary>
|
||||
public bool UseSelectedOrEnterMode(uint selectedObjectId)
|
||||
{
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] route=toolbar-or-queued-use UseSelectedOrEnterMode guid=0x{selectedObjectId:X8}");
|
||||
}
|
||||
if (selectedObjectId != 0)
|
||||
return ActivateItem(selectedObjectId);
|
||||
return _interactionState.EnterUse();
|
||||
|
|
@ -666,11 +656,6 @@ public sealed class ItemInteractionController : IDisposable
|
|||
|
||||
public bool ActivateItem(uint itemGuid)
|
||||
{
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] entry:ActivateItem guid=0x{itemGuid:X8} isTargetModeActive={IsTargetModeActive}");
|
||||
}
|
||||
if (itemGuid == 0) return false;
|
||||
|
||||
if (IsTargetModeActive)
|
||||
|
|
@ -997,11 +982,6 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// </summary>
|
||||
public bool ExecuteConfirmedUse(uint objectId)
|
||||
{
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] route=confirmed-use ExecuteConfirmedUse guid=0x{objectId:X8}");
|
||||
}
|
||||
if (objectId == 0u || (_requestUse is null && _sendUse is null))
|
||||
return false;
|
||||
if (!EnsureInventoryRequestReady())
|
||||
|
|
|
|||
|
|
@ -377,13 +377,6 @@ public sealed class SelectedObjectController : IRetainedPanelController
|
|||
? $"{stackSize} {objectName}"
|
||||
: objectName;
|
||||
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] ApplySelection guid=0x{g:X8} stackSizeOperand={stackSize} "
|
||||
+ $"objectName={objectName ?? "null"} builtLabel={_currentName ?? "null"}");
|
||||
}
|
||||
|
||||
// ── 3. Selection overlay: brief flash (retail container ObjectSelected
|
||||
// = Pause(0.25s)→Normal). "StackedItemSelected" for stacks. ──────────────
|
||||
SetOverlayState(stackSize > 1u
|
||||
|
|
@ -410,18 +403,6 @@ public sealed class SelectedObjectController : IRetainedPanelController
|
|||
_splitQuantity.Reset(stackSize, initialValue: seed);
|
||||
if (_stackSizeEntry is not null) _stackSizeEntry.Visible = true;
|
||||
if (_stackSizeSlider is not null) _stackSizeSlider.Visible = true;
|
||||
if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] ApplySelection guid=0x{g:X8} sliderVisible=true "
|
||||
+ $"isVendorSplitExempt={vendorSplitExempt} maxSplitSize={stackSize} seed={seed}");
|
||||
}
|
||||
}
|
||||
else if (VendorDiagnostics.DumpVendorEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[vendor-diag] ApplySelection guid=0x{g:X8} sliderVisible=false "
|
||||
+ $"failingPredicate=stackSize<=1u stackSize={stackSize}");
|
||||
}
|
||||
|
||||
// ── 4. Health: query, and show the meter only if real health is already known.
|
||||
|
|
|
|||
|
|
@ -21,15 +21,13 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
|
|||
private readonly ILiveEntityTeardownCoordinator _teardown;
|
||||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
private readonly DormantLiveEntityStore _dormant;
|
||||
private readonly Action<string>? _diagnostic;
|
||||
|
||||
public LiveEntityDeletionController(
|
||||
LiveEntityRuntime runtime,
|
||||
RuntimeEntityObjectLifetime entityObjects,
|
||||
ILiveEntityTeardownCoordinator teardown,
|
||||
ILocalPlayerIdentitySource identity,
|
||||
DormantLiveEntityStore? dormant = null,
|
||||
Action<string>? diagnostic = null)
|
||||
DormantLiveEntityStore? dormant = null)
|
||||
{
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
_entityObjects = entityObjects
|
||||
|
|
@ -37,7 +35,6 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
|
|||
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
|
||||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
_dormant = dormant ?? new DormantLiveEntityStore();
|
||||
_diagnostic = diagnostic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -64,13 +61,6 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
|
|||
if (removed)
|
||||
{
|
||||
_dormant.RemoveExact(delete);
|
||||
_diagnostic?.Invoke(
|
||||
$"live: delete guid=0x{delete.Guid:X8} instSeq={delete.InstanceSequence}");
|
||||
}
|
||||
else if (removedDormant)
|
||||
{
|
||||
_diagnostic?.Invoke(
|
||||
$"live: delete dormant guid=0x{delete.Guid:X8} instSeq={delete.InstanceSequence}");
|
||||
}
|
||||
return removed || removedDormant;
|
||||
}
|
||||
|
|
@ -95,8 +85,6 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
|
|||
return false;
|
||||
|
||||
_dormant.Retain(snapshot);
|
||||
_diagnostic?.Invoke(
|
||||
$"live: dormant guid=0x{candidate.ServerGuid:X8} instSeq={candidate.Generation}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,6 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
|
|||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
private readonly LiveEntityDeletionController _deletion;
|
||||
private readonly DormantLiveEntityStore _dormant;
|
||||
private readonly Action<string>? _diagnostic;
|
||||
/// <summary>
|
||||
/// C3c: the graphical first-entry drive pump — pumped at the end of each
|
||||
/// Create transaction so a fresh residence drives its conductor
|
||||
|
|
@ -219,7 +218,6 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
|
|||
ILocalPlayerIdentitySource identity,
|
||||
LiveEntityDeletionController deletion,
|
||||
DormantLiveEntityStore? dormant = null,
|
||||
Action<string>? diagnostic = null,
|
||||
RuntimeFirstEntryDriveController? firstEntry = null,
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
|
||||
{
|
||||
|
|
@ -236,7 +234,6 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
|
|||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
_deletion = deletion ?? throw new ArgumentNullException(nameof(deletion));
|
||||
_dormant = dormant ?? new DormantLiveEntityStore();
|
||||
_diagnostic = diagnostic;
|
||||
_firstEntry = firstEntry;
|
||||
_acceptedPositionDrive = acceptedPositionDrive;
|
||||
}
|
||||
|
|
@ -465,11 +462,6 @@ AppearanceSynchronization:
|
|||
ChildUnparentDisposition disposition =
|
||||
_relationships.OnChildBecameUnparented(update.Guid);
|
||||
bool accepted = disposition is not ChildUnparentDisposition.Superseded;
|
||||
if (accepted)
|
||||
{
|
||||
_diagnostic?.Invoke(
|
||||
$"live: pickup guid=0x{update.Guid:X8} instSeq={update.InstanceSequence} posSeq={update.PositionSequence}");
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
|
|
@ -601,7 +593,6 @@ AppearanceSynchronization:
|
|||
if (candidates.Count == 0)
|
||||
return;
|
||||
|
||||
int projected = 0;
|
||||
lock (_datLock)
|
||||
{
|
||||
foreach (RuntimeEntityRecord candidate in candidates)
|
||||
|
|
@ -623,26 +614,21 @@ AppearanceSynchronization:
|
|||
candidate.Snapshot,
|
||||
LiveProjectionPurpose.CreateSupersessionRecovery))
|
||||
{
|
||||
projected++;
|
||||
projectedCanonicalAppearance = true;
|
||||
}
|
||||
}
|
||||
else if (record?.WorldEntity is not null
|
||||
&& record.InitialHydrationCompleted)
|
||||
{
|
||||
if (_runtime.RebucketLiveEntity(
|
||||
record.ServerGuid,
|
||||
record.ProjectionCellId))
|
||||
{
|
||||
projected++;
|
||||
}
|
||||
_runtime.RebucketLiveEntity(
|
||||
record.ServerGuid,
|
||||
record.ProjectionCellId);
|
||||
}
|
||||
else if (ProjectExact(
|
||||
candidate,
|
||||
candidate.Snapshot,
|
||||
LiveProjectionPurpose.SpatialRecovery))
|
||||
{
|
||||
projected++;
|
||||
projectedCanonicalAppearance = true;
|
||||
}
|
||||
|
||||
|
|
@ -658,21 +644,14 @@ AppearanceSynchronization:
|
|||
|
||||
if (record is not null
|
||||
&& _runtime.IsCurrentRecord(record)
|
||||
&& record.AppearanceProjectionSynchronizationPending
|
||||
&& SynchronizeAppearance(
|
||||
record,
|
||||
record.ObjDescAuthorityVersion))
|
||||
&& record.AppearanceProjectionSynchronizationPending)
|
||||
{
|
||||
projected++;
|
||||
SynchronizeAppearance(
|
||||
record,
|
||||
record.ObjDescAuthorityVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projected > 0)
|
||||
{
|
||||
_diagnostic?.Invoke(
|
||||
$"live: re-projected {projected} server object(s) into landblock 0x{loadedLandblockId:X8}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnsureWorldOrigin(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue