fix(chat): consolidated-review fixes — retail /help Detail extraction, seam wiring test
SHOULD-FIX 1: RetailClientCommandCatalog's ~45 catalog leaf verbs were showing acdream-authored Summary text for /help <verb> instead of retail's own Detail_HelpType(2) text. Byte-swept every Help* handler against the PDB-paired acclient.exe (verified MATCH), confirmed each Detail/Summary branch by reading the actual decompiled if/else shape (address order and string length both proved unreliable alone), and fixed a sweep_weenie_strings.py 800-char truncation bug that silently dropped several longer Detail branches. Resolved every ambiguous CmdHashData-registered verb (hor/hr/hom/hoa/alh/ah/friends_add/ friends_remove/squelch/unsquelch) by reading for Binary Ninja's nullptr-4th-arg decompiler artifact instead of trusting it. Coverage: 42 of 47 distinct catalog Definitions verbatim-extracted, 4 confirmed-null (index/clist/on/off register with a genuinely null help pointer — DoHelp falls to UnknownCommand for these, now reproduced), 1 honest UNVERIFIED (messagetypes builds its text from a runtime enum table, not a static string). ChatCommandRouter now prefers retail Detail text over the catalog summary; RetailCommandHelpTable's class doc no longer overclaims its own scope. SHOULD-FIX 2: extracted the a5a7eb4f-class OnInterfaceText wiring into a testable CreateChatViewModel method and added ComposedChatViewModelWiresOnInterfaceTextToSpewBox, which the prior FakeFactory-based test suite could never exercise. SHOULD-FIX 3: retires register row AP-113. DoLifestone/DoMarketplace print their own 0x1A refusal text (byte-recovered, UTF-16LE) instead of falling through to the generic 0x26 fallback; ChatCommandRouter's comment corrected to state the fallback's real scope. SHOULD-FIX 4: corrected the divergence register's stale AP section header sentence about AP-190's opacity default (refuted bycc582899). NITs: (a) HeadlessStaticStateAudit routes through the injected HeadlessDiagnosticWriter instead of Console.WriteLine; (b) a bounded 300-pump liveness diagnostic on the IsQuiescent conductor gate (no retry, no behavior change); (c) fixed the #365 hydration test's doc comment contradiction against diagnosis §8; (d) the 0x26 fallback dispatches on WeenieErrorMessages' own Type instead of hardcoding ClientLocal. Full Release suite: 12,553 passed / 4 skipped / 0 failed (baseline03404b71: 12,542/4/0; net +11 tests, zero regressions). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
03404b7121
commit
f7a6f46ba0
15 changed files with 947 additions and 85 deletions
|
|
@ -42,10 +42,14 @@ internal sealed class HeadlessProcessHost : IDisposable
|
|||
throw new HeadlessConfigurationException(
|
||||
"Direct credentials require exactly one configured session.");
|
||||
}
|
||||
HeadlessStaticStateAudit.ValidateProcessIsolation(
|
||||
configuration.Sessions.Count);
|
||||
|
||||
// Consolidated-review round (2026-08-10), NIT (a): construct the
|
||||
// diagnostics writer BEFORE the audit call so its single-session
|
||||
// "probes enabled" line routes through the same structured stream
|
||||
// every other headless diagnostic uses, instead of a bare
|
||||
// Console.WriteLine that bypassed it.
|
||||
_diagnostics = new HeadlessDiagnosticWriter(diagnostics);
|
||||
HeadlessStaticStateAudit.ValidateProcessIsolation(
|
||||
configuration.Sessions.Count, _diagnostics);
|
||||
var credentials = new HeadlessCredentialResolver(
|
||||
standardInput,
|
||||
paths.ConfigDirectory);
|
||||
|
|
|
|||
|
|
@ -675,7 +675,14 @@ internal sealed class HeadlessSessionHost : IDisposable
|
|||
Runtime,
|
||||
content,
|
||||
_firstEntryDrive,
|
||||
_acceptedPositionDrive);
|
||||
_acceptedPositionDrive,
|
||||
// Consolidated-review round (2026-08-10), NIT (b): the
|
||||
// SAME session-labelled diagnostics stream every other
|
||||
// producer in this class writes into.
|
||||
onNonQuiescentStall: message => _diagnostics.Message(
|
||||
_descriptor.Id,
|
||||
message,
|
||||
Runtime.Generation.Value));
|
||||
_worldProjection = projection;
|
||||
worldProjection = projection;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -617,18 +617,34 @@ internal sealed class HeadlessSessionWorldProjection
|
|||
/// resolves promptly.
|
||||
/// </summary>
|
||||
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
|
||||
private readonly Action<string>? _onNonQuiescentStall;
|
||||
private uint _requestedLocalPlayerCell;
|
||||
|
||||
/// <summary>
|
||||
/// Consolidated-review round (2026-08-10), NIT (b): a purely
|
||||
/// diagnostic trip-wire, not a behavior change. At the host's per-tick
|
||||
/// pump cadence this is on the order of many seconds — generous enough
|
||||
/// that no legitimate multi-tick collision-generation sequence should
|
||||
/// ever cross it, so crossing it means something is genuinely stuck
|
||||
/// (a publication that never seals, an admission that never clears).
|
||||
/// </summary>
|
||||
private const int NonQuiescentStallPumpThreshold = 300;
|
||||
|
||||
private int _nonQuiescentPumpCount;
|
||||
private bool _reportedNonQuiescentStall;
|
||||
|
||||
internal HeadlessSessionWorldProjection(
|
||||
GameRuntime runtime,
|
||||
HeadlessProcessContentOwner.HeadlessProcessContentLease content,
|
||||
RuntimeFirstEntryDriveController? firstEntry = null,
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null,
|
||||
Action<string>? onNonQuiescentStall = null)
|
||||
: this(
|
||||
runtime,
|
||||
new HeadlessCollisionNeighborhood(runtime, content),
|
||||
firstEntry,
|
||||
acceptedPositionDrive)
|
||||
acceptedPositionDrive,
|
||||
onNonQuiescentStall)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -636,7 +652,8 @@ internal sealed class HeadlessSessionWorldProjection
|
|||
GameRuntime runtime,
|
||||
IHeadlessCollisionNeighborhood collision,
|
||||
RuntimeFirstEntryDriveController? firstEntry = null,
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null,
|
||||
Action<string>? onNonQuiescentStall = null)
|
||||
{
|
||||
_runtime = runtime
|
||||
?? throw new ArgumentNullException(nameof(runtime));
|
||||
|
|
@ -644,6 +661,7 @@ internal sealed class HeadlessSessionWorldProjection
|
|||
?? throw new ArgumentNullException(nameof(collision));
|
||||
_firstEntry = firstEntry;
|
||||
_acceptedPositionDrive = acceptedPositionDrive;
|
||||
_onNonQuiescentStall = onNonQuiescentStall;
|
||||
}
|
||||
|
||||
public void ProjectSpawn(
|
||||
|
|
@ -776,7 +794,23 @@ internal sealed class HeadlessSessionWorldProjection
|
|||
// publication work each tick (its own mutating side effect), so
|
||||
// gating it too would make the neighborhood itself never converge.
|
||||
if (!_collision.IsQuiescent)
|
||||
{
|
||||
// NIT (b): diagnostic-only trip-wire, no retry and no behavior
|
||||
// change -- the gate above keeps refusing to drive the
|
||||
// conductor exactly as it always has. Fires ONCE per stall
|
||||
// episode so a hang shows up in the diagnostics stream instead
|
||||
// of reading as "still working" indefinitely.
|
||||
if (++_nonQuiescentPumpCount >= NonQuiescentStallPumpThreshold
|
||||
&& !_reportedNonQuiescentStall)
|
||||
{
|
||||
_reportedNonQuiescentStall = true;
|
||||
_onNonQuiescentStall?.Invoke(FormattableString.Invariant(
|
||||
$"collision neighborhood non-quiescent for {_nonQuiescentPumpCount} pumps, requested landblock=0x{_requestedLocalPlayerCell:X8}"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
_nonQuiescentPumpCount = 0;
|
||||
_reportedNonQuiescentStall = false;
|
||||
_firstEntry?.DriveAll();
|
||||
_acceptedPositionDrive?.Advance();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Reflection;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Headless.Configuration;
|
||||
using AcDream.Headless.Diagnostics;
|
||||
|
||||
namespace AcDream.Headless.Hosting;
|
||||
|
||||
|
|
@ -22,8 +23,18 @@ namespace AcDream.Headless.Hosting;
|
|||
/// </remarks>
|
||||
internal static class HeadlessStaticStateAudit
|
||||
{
|
||||
internal static void ValidateProcessIsolation(int sessionCount)
|
||||
/// <summary>
|
||||
/// Consolidated-review round (2026-08-10), NIT (a): <paramref name="diagnostics"/>
|
||||
/// is the SAME structured writer every other headless diagnostic uses
|
||||
/// (<see cref="HeadlessProcessHost"/>'s <c>_diagnostics</c> field, now
|
||||
/// constructed before this call rather than after it) — the raw
|
||||
/// <c>Console.WriteLine</c> this replaced bypassed the session-labelled
|
||||
/// JSON stream every other producer writes into.
|
||||
/// </summary>
|
||||
internal static void ValidateProcessIsolation(
|
||||
int sessionCount, HeadlessDiagnosticWriter diagnostics)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(diagnostics);
|
||||
var enabled = new List<string>();
|
||||
foreach (PropertyInfo property in typeof(PhysicsDiagnostics)
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Static)
|
||||
|
|
@ -53,8 +64,10 @@ internal static class HeadlessStaticStateAudit
|
|||
|
||||
if (sessionCount == 1)
|
||||
{
|
||||
Console.WriteLine(FormattableString.Invariant(
|
||||
$"[headless-audit] single-session process — process-global physics probes enabled: {string.Join(", ", enabled)}"));
|
||||
diagnostics.Message(
|
||||
sessionId: "process",
|
||||
eventName: FormattableString.Invariant(
|
||||
$"headless-audit: single-session process — process-global physics probes enabled: {string.Join(", ", enabled)}"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue