fix(ui): night-round review — F11/F13/F14/F15 one-liners

F11: logs when a Map tab town-marker's template resolves to something
other than a UiButton — that path previously silently skipped the
TooltipText write with no diagnostic, leaving a mounted-but-empty
tooltip popup indistinguishable from "no template configured".

F13: RetailSkillFormula.FormatFormula now reads Attribute1Multiplier/
Attribute2Multiplier/AdditiveBonus/Divisor through the SAME unsigned
reinterpretation TryCalculate already uses (this class's own doc
comment already stated the invariant; FormatFormula just didn't follow
it). A high-bit-set value would previously both mis-gate hasAttr1/
hasAttr2 and print a negative number, out of sync with what
TryCalculate actually computes with for the same formula. Added
regression tests, empirically verified to fail without the fix.

F14: documented the RefreshHouseMarker gap rather than guessing at the
byte-decode — Position::get_outside_cell_id @0x004527b0 is itself
BN-mangled (its `(eax_2 - eax_2) & objcell_id` return is the same
decompiler-obscures-a-real-conditional artifact class this round hit
elsewhere) and depends on LandDefs::adjust_to_outside, a genuinely
larger port than this round's other findings. HousePosition is wired
() => null in production today (ISSUES #413's remaining scope), so
this method is currently unreachable; left a TODO citing the retail
call chain for whenever that lands.

F15: fixed RefreshCoordinatesAndPlayerMarker's gate to AND-on-both-
present, matching gmMapUI::Update @0x004a2078's exact
`if (m_pCoordinateText != 0 && m_pPlayerLocationIcon != 0)` condition.
The prior `_coordinateText is null && _playerIcon is null` check only
skipped when BOTH were absent (proceeding whenever EITHER was
present), letting coordinate text and the player marker update
independently instead of as the single gated unit retail treats them
as. Added a regression test (player-icon template resolution failure
must also skip the coordinate-text write), empirically verified to
fail without the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-17 05:06:17 +02:00
parent c403f57815
commit 0b0c7aa485
4 changed files with 172 additions and 18 deletions

View file

@ -130,10 +130,24 @@ internal static class RetailSkillFormula
{
ArgumentNullException.ThrowIfNull(formula);
bool hasAttr1 = formula.Attribute1Multiplier >= 1
&& formula.Attribute1 != 0;
bool hasAttr2 = formula.Attribute2Multiplier >= 1
&& formula.Attribute2 != 0;
// F13 (night-round review): read the SAME unsigned reinterpretation
// TryCalculate above uses — this class's own doc comment already
// states the invariant ("DAT reader fields are signed storage
// views... deliberately reinterpreted as retail's unsigned W/X/Y/Z
// words") but this method previously read the raw signed int
// fields directly. A high-bit-set multiplier/divisor/bonus would
// both mis-gate hasAttr1/hasAttr2 (reads negative, failing the
// >= 1 check TryCalculate's unsigned reinterpretation would have
// passed) and print the wrong (negative) number — out of sync with
// the value TryCalculate actually computes with for the SAME
// formula.
uint x = unchecked((uint)formula.Attribute1Multiplier);
uint y = unchecked((uint)formula.Attribute2Multiplier);
uint w = unchecked((uint)formula.AdditiveBonus);
uint divisor = unchecked((uint)formula.Divisor);
bool hasAttr1 = x >= 1 && formula.Attribute1 != 0;
bool hasAttr2 = y >= 1 && formula.Attribute2 != 0;
if (!hasAttr1 && !hasAttr2)
return null;
@ -144,9 +158,9 @@ internal static class RetailSkillFormula
if (hasAttr1)
{
string name1 = AttributeName(formula.Attribute1);
text.Append(formula.Attribute1Multiplier <= 1
text.Append(x <= 1
? name1
: $"({formula.Attribute1Multiplier} x {name1})");
: $"({x} x {name1})");
if (hasAttr2)
text.Append(" + ");
}
@ -154,17 +168,17 @@ internal static class RetailSkillFormula
if (hasAttr2)
{
string name2 = AttributeName(formula.Attribute2);
text.Append(formula.Attribute2Multiplier <= 1
text.Append(y <= 1
? name2
: $"({formula.Attribute2Multiplier} x {name2})");
: $"({y} x {name2})");
}
if (hasAttr1 && hasAttr2)
text.Append(')');
if (formula.Divisor != 1)
text.Append($" / {formula.Divisor}");
if (formula.AdditiveBonus != 0)
text.Append($"+{formula.AdditiveBonus}");
if (divisor != 1)
text.Append($" / {divisor}");
if (w != 0)
text.Append($"+{w}");
text.Append(" )");
return text.ToString();
}