fix(ui): morning gate — House tab renders retail's TWO houseless lines, not one
User finding 2 (retail screenshot, houseless character): the House tab
shows "You do not currently own a house." ABOVE "You may buy another
house immediately." — ours showed only the second line, and the prior
session had REFUTED the first line outright ("no such string exists
anywhere in the 2013 dump").
Re-derivation: the string exists in the binary at data_7ab688 — it is
gmHouseUI::DisplayBuyPayment @0x004a2b30's HOUSELESS branch. Two
compounding misreads hid it: (a) DisplayBuyPayment was mislabeled
houseless-silent, but its m_pHouseData gate only selects WHICH text
(jne 0x4a2b63) — the ListBox emit (@0x004a2b80 onward,
AddItemFromTemplateList + SetTextWithFont) runs in BOTH branches; and
(b) BN's pseudo-C renders both push-literal operands as spurious
&vftable.RecvNotice_* symbol matches (the TS-85/F3 artifact class), so
text sweeps of the dump find nothing — capstone byte-decode of the
PDB-paired binary resolves houseless @0x004a2b57 push 0x7ab688 =
"You do not currently own a house." and owned @0x004a2b63 push
0x7ab65c = "The purchase price for this dwelling is:\n" (+
HousePaymentList::ComposeText, still #413 item-3 scope). The morning
brief's alternate DAT-string-table hypothesis was checked and is NOT
the mechanism — plain exe string-pool literal.
RuntimeHouseState.Recompute now renders the houseless case as retail's
exact two lines in gmHouseUI::Update's fixed builder order
(DisplayBuyPayment first, DisplayPurchaseTimeText last); the owned case
is unchanged (its DisplayBuyPayment content needs ComposeText, #413
item 3). Class doc + ISSUES #413 corrected honestly — the user's retail
evidence supersedes the earlier refutation. Runtime house tests updated
to pin both lines; 9/9 pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9d9280a069
commit
ef567bfa20
3 changed files with 139 additions and 76 deletions
|
|
@ -6,10 +6,13 @@ using AcDream.Runtime.Gameplay;
|
|||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// House-tab conformance (Batch C, 2026-08-17): the ONE decomp-verified
|
||||
/// line <c>gmHouseUI::DisplayPurchaseTimeText @0x004a3110</c> emits for a
|
||||
/// houseless/fresh character, and the wait-period-not-expired case that
|
||||
/// stays empty (unrecoverable strftime format, ISSUES #413 item 2).
|
||||
/// House-tab conformance (Batch C, 2026-08-17; two-line correction at the
|
||||
/// same-day morning gate round): the houseless case renders retail's exact
|
||||
/// TWO lines in builder order — <c>gmHouseUI::DisplayBuyPayment
|
||||
/// @0x004a2b30</c>'s houseless branch ("You do not currently own a
|
||||
/// house.", byte-decoded <c>data_7ab688</c> — the m_pHouseData gate only
|
||||
/// selects WHICH text; the emit is unconditional) followed by
|
||||
/// <c>DisplayPurchaseTimeText @0x004a3110</c>'s wait-period line.
|
||||
/// </summary>
|
||||
public sealed class RuntimeHouseStateTests
|
||||
{
|
||||
|
|
@ -28,7 +31,7 @@ public sealed class RuntimeHouseStateTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void HouseStatus_FreshCharacterWithNoTimestamp_ShowsBuyImmediatelyLine()
|
||||
public void HouseStatus_FreshCharacterWithNoTimestamp_ShowsBothHouselessLines()
|
||||
{
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = Self, Type = ItemType.Creature });
|
||||
|
|
@ -40,7 +43,16 @@ public sealed class RuntimeHouseStateTests
|
|||
house.ApplyHouseStatus(weenieError: 0u, Self);
|
||||
|
||||
Assert.True(house.HasReceivedNotice);
|
||||
Assert.Equal(["You may buy another house immediately."], house.Lines);
|
||||
// 2026-08-17 morning gate finding 2 (user retail screenshot):
|
||||
// BOTH lines, in gmHouseUI::Update's fixed builder order —
|
||||
// DisplayBuyPayment's houseless literal first, then
|
||||
// DisplayPurchaseTimeText's expired-branch literal.
|
||||
Assert.Equal(
|
||||
[
|
||||
"You do not currently own a house.",
|
||||
"You may buy another house immediately.",
|
||||
],
|
||||
house.Lines);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -98,7 +110,11 @@ public sealed class RuntimeHouseStateTests
|
|||
clock.Advance(TimeSpan.FromDays(29));
|
||||
house.ApplyHouseStatus(weenieError: 0u, Self);
|
||||
|
||||
string line = Assert.Single(house.Lines);
|
||||
// Houseless -> DisplayBuyPayment's line precedes the purchase-time
|
||||
// line; the not-expired branch supplies the second.
|
||||
Assert.Equal(2, house.Lines.Count);
|
||||
Assert.Equal("You do not currently own a house.", house.Lines[0]);
|
||||
string line = house.Lines[1];
|
||||
Assert.StartsWith("You may buy another landscape house at ", line);
|
||||
Assert.EndsWith(". This restriction does not apply to apartments.", line);
|
||||
}
|
||||
|
|
@ -124,12 +140,14 @@ public sealed class RuntimeHouseStateTests
|
|||
house.ApplyHouseStatus(weenieError: 0u, Self);
|
||||
|
||||
DateTime expectedExpiry = purchaseTime.AddSeconds(0x278d00).UtcDateTime;
|
||||
string line = Assert.Single(house.Lines);
|
||||
Assert.Contains(expectedExpiry.ToString(System.Globalization.CultureInfo.CurrentCulture), line);
|
||||
Assert.Equal(2, house.Lines.Count);
|
||||
Assert.Contains(
|
||||
expectedExpiry.ToString(System.Globalization.CultureInfo.CurrentCulture),
|
||||
house.Lines[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HouseStatus_TimestampPastThirtyDayWindow_ShowsBuyImmediatelyLine()
|
||||
public void HouseStatus_TimestampPastThirtyDayWindow_ShowsBothHouselessLines()
|
||||
{
|
||||
var clock = new ManualTimeProvider();
|
||||
var objects = new ClientObjectTable();
|
||||
|
|
@ -143,7 +161,12 @@ public sealed class RuntimeHouseStateTests
|
|||
clock.Advance(TimeSpan.FromDays(31));
|
||||
house.ApplyHouseStatus(weenieError: 0u, Self);
|
||||
|
||||
Assert.Equal(["You may buy another house immediately."], house.Lines);
|
||||
Assert.Equal(
|
||||
[
|
||||
"You do not currently own a house.",
|
||||
"You may buy another house immediately.",
|
||||
],
|
||||
house.Lines);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -170,7 +193,12 @@ public sealed class RuntimeHouseStateTests
|
|||
|
||||
house.ApplyHouseStatus(weenieError: 0u, Self);
|
||||
|
||||
Assert.Equal(["You may buy another house immediately."], house.Lines);
|
||||
Assert.Equal(
|
||||
[
|
||||
"You do not currently own a house.",
|
||||
"You may buy another house immediately.",
|
||||
],
|
||||
house.Lines);
|
||||
}
|
||||
|
||||
private static GameEvents.HouseData SampleHouseData() => new(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue