fix #270 closeout: strip investigation probes; close the issue

User-verified: casting fixed (exhaustion-edge gate) and monster attack
animations restored (spawn settle placement + lost-cell retry). Final
session evidence: 14/15 spawn settles grounded; Falling-refusal spam
collapsed 2,954 -> 15 transient pre-settle lines.

Strips the [UM-ACT]/[MT-FAIL]/[SPAWN-PLACE]/[remote-edge] probes, the
MotionInterpreter.DiagnosticGuid plumbing, and the two throwaway probe
tests (motion-table attack sweep, vitae color dump - both findings are
recorded in ISSUES/research). Complete Release suite: 10,030 passed /
5 skips / 0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-31 07:26:57 +02:00
parent 21b3a3f3d3
commit bb1640f777
7 changed files with 3 additions and 263 deletions

View file

@ -1,124 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using AcDream.Core.Physics.Motion;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;
namespace AcDream.App.Tests.Physics;
// THROWAWAY probe (#270): sweep every MotionTable in the installed portal
// dat and attempt the EXACT wire dispatch observed in the stuck-attack
// session (style 0x8000003C, ready substate, target attack 0x400000D3 at
// speed 2.0). A silent GetObjectSequence failure = "monster attacks but the
// animation never fires". Delete after #270 closes.
public sealed class MotionTableAttackDispatchProbe
{
private sealed class NullLoader : AcDream.Core.Physics.IAnimationLoader
{
public DatReaderWriter.DBObjs.Animation? LoadAnimation(uint id) => null;
}
[Fact]
public void Sweep_attack_dispatch_across_all_motion_tables()
{
var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Documents", "Asheron's Call");
if (!Directory.Exists(datDir)) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
var loader = new NullLoader();
int total = 0, okCount = 0;
var failures = new List<string>();
var styleMisses = new List<string>();
foreach (var entry in dats.Portal.Tree.GetFilesInRange(0x09000000u, 0x09FFFFFFu))
{
if (!dats.Portal.TryGet<DatReaderWriter.DBObjs.MotionTable>(entry.Id, out var mt)
|| mt is null)
continue;
total++;
var cmt = new CMotionTable(mt);
var state = new MotionState
{
Style = 0x8000003Cu,
Substate = 0x41000003u,
};
var seq = new CSequence(loader);
// Seed exactly like the live path: install the combat style +
// ready substate first (SetDefaultState equivalent would use the
// table default; the wire showed the monsters already standing in
// style 0x3C ready).
bool ok;
try
{
ok = cmt.GetObjectSequence(0x400000D3u, state, seq, 2.0f, out _, stopCall: false);
}
catch (Exception ex)
{
failures.Add($"0x{entry.Id:X8} THREW {ex.GetType().Name}");
continue;
}
if (ok) okCount++;
else
{
// Distinguish "table has no 0x3C style at all" (never a
// combat monster) from "style exists but dispatch failed".
bool hasStyle = mt.StyleDefaults.ContainsKey(
(DatReaderWriter.Enums.MotionCommand)0x8000003Cu);
if (hasStyle)
failures.Add($"0x{entry.Id:X8} FAILED (has 0x3C style)");
else
styleMisses.Add($"0x{entry.Id:X8}");
}
}
var sb = new StringBuilder();
sb.AppendLine($"total={total} ok={okCount} failedWithStyle={failures.Count} noStyle={styleMisses.Count}");
foreach (var f in failures) sb.AppendLine(f);
// For the first few failing tables: dump what IS authored for the
// CastSpell substate (0xD3) so "un-authored data" vs "lookup bug"
// is decidable offline.
int dumped = 0;
foreach (var f in failures)
{
if (dumped >= 3 || !f.Contains("FAILED")) continue;
uint id = Convert.ToUInt32(f.Substring(2, 8), 16);
if (!dats.Portal.TryGet<DatReaderWriter.DBObjs.MotionTable>(id, out var mt) || mt is null)
continue;
dumped++;
sb.AppendLine($"--- table 0x{id:X8} DefaultStyle=0x{(uint)mt.DefaultStyle:X8}");
foreach (var kv in mt.Cycles)
{
uint key = (uint)kv.Key;
if ((key & 0xFFFFFFu) == 0xD3u || (key & 0xFFFFu) == 0xD3u)
sb.AppendLine($" cycle key=0x{key:X8}");
}
foreach (var kv in mt.Links)
{
uint key = (uint)kv.Key;
sb.AppendLine($" linkFrom key=0x{key:X8} targets={kv.Value.MotionData.Count}");
foreach (var t in kv.Value.MotionData.Keys)
{
uint tk = (uint)t;
if ((tk & 0xFFFFFFu) == 0xD3u)
sb.AppendLine($" -> target 0x{tk:X8} (CASTSPELL)");
}
}
}
File.WriteAllText(
Path.Combine(AppContext.BaseDirectory, "attack-dispatch-sweep.txt"),
sb.ToString());
var outCopy = Environment.GetEnvironmentVariable("ACDREAM_PROBE_OUT");
if (!string.IsNullOrEmpty(outCopy))
File.WriteAllText(outCopy, sb.ToString());
}
}

View file

@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using AcDream.App.UI.Layout;
using DatReaderWriter;
using DatReaderWriter.Options;
using Xunit;
namespace AcDream.App.Tests.UI.Layout;
// THROWAWAY probe (#268): dump the authored 0x1B font-color arrays for the
// character-panel footer labels so we can read retail's vitae parenthetical
// color (AppendTextWithFont color index 3, gmSkillUI 0x0049b972). Delete
// after the color constant is captured.
public sealed class VitaeColorDumpProbe
{
[Fact]
public void Dump_footer_font_color_arrays()
{
var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Documents", "Asheron's Call");
if (!Directory.Exists(datDir)) return;
var sb = new StringBuilder();
using var dats = new DatCollection(datDir, DatAccessType.Read);
foreach (uint layoutId in new[] { 0x2100002Eu, 0x2100002Cu })
{
var root = LayoutImporter.ImportInfos(dats, layoutId);
if (root is null) { sb.AppendLine($"layout {layoutId:X8}: NOT FOUND"); continue; }
sb.AppendLine($"=== layout {layoutId:X8} ===");
Walk(root, sb);
}
var outPath = Path.Combine(AppContext.BaseDirectory, "vitae-color-dump.txt");
File.WriteAllText(outPath, sb.ToString());
// Also drop a copy next to the repo artifacts when resolvable.
var repoCopy = Environment.GetEnvironmentVariable("ACDREAM_PROBE_OUT");
if (!string.IsNullOrEmpty(repoCopy))
File.WriteAllText(repoCopy, sb.ToString());
}
private static void Walk(ElementInfo e, StringBuilder sb)
{
if (e.TryGetEffectiveProperty(0x1Bu, out var color))
{
if (color.Kind == UiPropertyKind.Array && color.ArrayValue.Count > 1)
{
sb.Append($"element {e.Id:X8} 0x1B array[{color.ArrayValue.Count}]:");
for (int i = 0; i < color.ArrayValue.Count; i++)
{
var v = color.ArrayValue[i];
if (v.Kind == UiPropertyKind.Color)
{
var c = v.ColorValue;
sb.Append($" [{i}]=A{c.Alpha:D3},R{c.Red:D3},G{c.Green:D3},B{c.Blue:D3}");
}
else
{
sb.Append($" [{i}]=kind:{v.Kind}");
}
}
sb.AppendLine();
}
else if (color.Kind == UiPropertyKind.Color)
{
var c = color.ColorValue;
sb.AppendLine(
$"element {e.Id:X8} 0x1B single: A{c.Alpha:D3},R{c.Red:D3},G{c.Green:D3},B{c.Blue:D3}");
}
}
foreach (var child in e.Children)
Walk(child, sb);
}
}