fix(ui): #372 — Chat filter labels resolve from their real table 0x2300000D
The 13 ID_ChatOption_TextFilter_* labels (and their _Desc tooltips) live
in string table 0x2300000D, not the 0x23000003 options table the section
headers and slider labels use. Dat-verified: the targeted sweep missed
(0x23000001-0A), the control key resolved ('Auto Target' — machinery
fine), and the exhaustive all-tables sweep (ProbeFilterLabelHome, now a
permanent env-gated probe) hit exactly once: 0x2300000D -> 'Combat'. The
initializer decomp confirms the hash KEYS are the symbol names verbatim
(the vftable-member operands at 0x006f04cd are the known pooled-string
artifact); only the research doc's table attribution was inferred rather
than dat-verified — corrected in §8.
All 13 rows now render their captions instead of the honest-fallback
blanks the first connected gate saw. Full Release suite green (one
Core.Net loss-simulation timing flake on the first run, green targeted
and on rerun).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
057d8cd703
commit
348d794359
3 changed files with 66 additions and 3 deletions
|
|
@ -625,7 +625,7 @@ opt-in).
|
|||
push low; call AddChild` sequences (`decode_addchild.py` over the raw section bytes; masks
|
||||
are 64-bit, `high:low`):
|
||||
|
||||
| Row | Mask (high:low) | Label global | Label literal (table `0x23000003`) |
|
||||
| Row | Mask (high:low) | Label global | Label literal (table `0x2300000D` — CORRECTED 2026-08-11 at #372: the TextFilter label/tooltip family lives in its OWN table, dat-verified by exhaustive sweep; the original 0x23000003 attribution was inferred) |
|
||||
|---|---|---|---|
|
||||
| **0** | `0x00000000_83912021` | `ID_ChatOption_TextFilter_Gameplay` (`0x0083E840`) | Gameplay |
|
||||
| 1 | `0x00000000_00600040` | `ID_ChatOption_TextFilter_Combat` (`0x0083E830`) | Combat |
|
||||
|
|
|
|||
|
|
@ -81,6 +81,17 @@ public static class ChatOptionsPageController
|
|||
|
||||
private const uint StringTableId = 0x23000003u;
|
||||
|
||||
/// <summary>#372 (2026-08-11): the <c>ID_ChatOption_TextFilter_*</c>
|
||||
/// label/tooltip family lives in its OWN string table — an exhaustive
|
||||
/// live-DAT sweep (OptionsPanelLiveMountProbeTests.ProbeFilterLabelHome)
|
||||
/// found the keys resolve ONLY in <c>0x2300000D</c>, not the
|
||||
/// <c>0x23000003</c> options table the section headers and slider labels
|
||||
/// use (the research doc's §8 table-attribution was inferred, not
|
||||
/// dat-verified — the keys themselves were always right). All 13 filter
|
||||
/// rows rendered blank-captioned at the first connected gate because of
|
||||
/// this one wrong table id.</summary>
|
||||
private const uint FilterStringTableId = 0x2300000Du;
|
||||
|
||||
/// <summary>The slider leaf inside either slider row template's subtree.</summary>
|
||||
private const uint SliderElementId = 0x1000021Cu;
|
||||
|
||||
|
|
@ -461,9 +472,9 @@ public static class ChatOptionsPageController
|
|||
continue; // research doc §5.2: the main window has no Gameplay row.
|
||||
|
||||
FilterRowSpec rowSpec = FilterRows[i];
|
||||
string? label = resolveString(StringTableId, DatStringResolver.ComputeHash(rowSpec.RetailLabelKey));
|
||||
string? label = resolveString(FilterStringTableId, DatStringResolver.ComputeHash(rowSpec.RetailLabelKey));
|
||||
string? tooltip = resolveString(
|
||||
StringTableId, DatStringResolver.ComputeHash(rowSpec.RetailLabelKey + "_Desc"));
|
||||
FilterStringTableId, DatStringResolver.ComputeHash(rowSpec.RetailLabelKey + "_Desc"));
|
||||
if (label is null)
|
||||
Console.WriteLine(
|
||||
$"[D.2b] ChatOptionsPageController: filter row label "
|
||||
|
|
|
|||
|
|
@ -87,6 +87,58 @@ public sealed class OptionsPanelLiveMountProbeTests
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>#372 minor half: the 13 ID_ChatOption_TextFilter_* labels fail
|
||||
/// to resolve in table 0x23000003 — sweep the plausible tables and key
|
||||
/// spellings against the live DAT to find their real home.</summary>
|
||||
[Fact]
|
||||
public void ProbeFilterLabelHome()
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_PROBE_LIVE_MOUNT") != "1")
|
||||
return;
|
||||
|
||||
var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||
?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"Documents",
|
||||
"Asheron's Call");
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var strings = new DatStringResolver(dats);
|
||||
|
||||
string[] keys =
|
||||
{
|
||||
"ID_ChatOption_TextFilter_Combat",
|
||||
"ID_ChatOption_TextFilter_Gameplay",
|
||||
"ID_ChatOptions_TextFilter_Combat", // plural-Options variant
|
||||
"ID_Chat_TextFilter_Combat",
|
||||
"ID_TextFilter_Combat",
|
||||
"TextFilter_Combat",
|
||||
};
|
||||
for (uint table = 0x23000001u; table <= 0x2300000Au; table++)
|
||||
{
|
||||
foreach (string key in keys)
|
||||
{
|
||||
string? hit = strings.Resolve(table, DatStringResolver.ComputeHash(key));
|
||||
if (hit is not null)
|
||||
Console.WriteLine($"[probe] table 0x{table:X8} key '{key}' -> '{hit}'");
|
||||
}
|
||||
}
|
||||
// Also: does the KNOWN-good Character-tab key family resolve where
|
||||
// documented, as a control?
|
||||
Console.WriteLine(
|
||||
"[probe] control ID_PlayerOption_AutoTarget in 0x23000003 -> "
|
||||
+ $"'{strings.Resolve(0x23000003u, DatStringResolver.ComputeHash("ID_PlayerOption_AutoTarget"))}'");
|
||||
|
||||
// Exhaustive: sweep EVERY string table in the local dat for the key.
|
||||
uint targetHash = DatStringResolver.ComputeHash("ID_ChatOption_TextFilter_Combat");
|
||||
foreach (uint tableId in dats.Local.GetAllIdsOfType<DatReaderWriter.DBObjs.StringTable>())
|
||||
{
|
||||
string? hit = strings.Resolve(tableId, targetHash);
|
||||
if (hit is not null)
|
||||
Console.WriteLine($"[probe] EXHAUSTIVE hit: table 0x{tableId:X8} -> '{hit}'");
|
||||
}
|
||||
Console.WriteLine("[probe] exhaustive sweep complete");
|
||||
}
|
||||
|
||||
private static void DumpTree(ElementInfo node, int depth, int maxDepth)
|
||||
{
|
||||
if (depth > maxDepth) return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue