diag(net): #260 outbound/command-gate probe + corrected issue framing

The two-agent investigation refuted #260's as-filed hypotheses: every
UseWithTarget was acked (the J5.2 use gate never latched), and the LOH
leak is bounded sawtooth churn - the real climb is ~2.25 GB of native/
GPU memory (WS 3,261 vs managed 1,015 MiB at wedge). The wedge evidence
also showed why it could hide: the live combat toggle routes through the
generation-gated runtime command seam, and every rejection exit in that
chain (Disposed / StaleGeneration / !IsInWorld at Validate, plus the
operations slot reading IsInWorld=false when unbound) is COMPLETELY
silent - no log, no event.

ACDREAM_PROBE_NET=1 (NetDiagnostics owner, PhysicsDiagnostics pattern)
now arms three probe families, all zero-cost when off:

- [net-out] per reliable send at the SendGameMessage chokepoint: opcode,
  GameAction type+sequence, fragment/packet sequence, managed thread id
  (two tids would prove the cross-thread ISAAC-desync hypothesis alone),
  and state; [net-out-EX] via an exception FILTER that logs without
  catching, so propagation is unchanged.
- [net-tick] 1 Hz cadence from WorldSession.Tick: inbound/s, queue
  depth, budget breaks, worst inter-tick gap (frame-stall witness),
  out/s, acks/s.
- [cmd-gate] every silent runtime-command rejection with expected-vs-
  view generation, lifecycle, and IsInWorld, plus the combat toggle
  result (whose Inactive exit reads a DIFFERENT IsInWorld source).

One walked-portal repro session with this probe distinguishes all
remaining #260 wedge hypotheses. ISSUES.md #260 rewritten to the
corrected two-root framing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-29 08:53:58 +02:00
parent ab3da28c34
commit 534bacbc23
4 changed files with 245 additions and 55 deletions

View file

@ -231,6 +231,17 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeCommandStatus.Inactive,
_ => RuntimeCommandStatus.Rejected,
};
// #260 probe: the toggle's own Inactive exit comes from a
// DIFFERENT IsInWorld source than Validate's (the operations
// slot, which reads false when unbound). Logging the result
// here separates "gate passed, toggle refused" from "gate
// rejected" — the two look identical to the player.
if (AcDream.Core.Net.NetDiagnostics.ProbeNet)
{
Console.WriteLine(
$"[cmd-gate] combat toggle result={result.Status}"
+ $" mode={result.Mode}");
}
}
else
{
@ -753,13 +764,31 @@ internal sealed class CurrentGameRuntimeCommandAdapter
RuntimeGenerationToken expectedGeneration,
bool requireWorld)
{
RuntimeCommandStatus status;
if (_view.Lifecycle.State == RuntimeLifecycleState.Disposed)
return RuntimeCommandStatus.Inactive;
if (expectedGeneration != _view.Generation)
return RuntimeCommandStatus.StaleGeneration;
if (requireWorld && !_session.IsInWorld)
return RuntimeCommandStatus.Inactive;
return RuntimeCommandStatus.Accepted;
status = RuntimeCommandStatus.Inactive;
else if (expectedGeneration != _view.Generation)
status = RuntimeCommandStatus.StaleGeneration;
else if (requireWorld && !_session.IsInWorld)
status = RuntimeCommandStatus.Inactive;
else
status = RuntimeCommandStatus.Accepted;
// #260 probe: a rejected generation-gated command is otherwise
// COMPLETELY silent (no log, no event) — which is exactly how the
// portal-network wedge hid. Log every rejection with the full gate
// state so the failing predicate names itself.
if (status != RuntimeCommandStatus.Accepted
&& AcDream.Core.Net.NetDiagnostics.ProbeNet)
{
Console.WriteLine(
$"[cmd-gate] REJECT status={status}"
+ $" expected={expectedGeneration}"
+ $" view={_view.Generation}"
+ $" lifecycle={_view.Lifecycle.State}"
+ $" inWorld={_session.IsInWorld}");
}
return status;
}
private RuntimeCommandResult Result(