feat(ui): Campaign AS AS5 — allegiance rank-title table, exam title bar + character panel name line (G9, narrows AP-109)
Ports the 17-function heritage×gender AllegianceSystem::GetTitle
@0x005B8DD0 dispatch and AllegianceData::GetFullName @0x005B6950 as
AllegianceRankTitleTable (src/AcDream.App/UI/Layout/), then wires both
retail call sites: the examination window's title bar
(AppraisalUiController.BuildCharacterTitleBarName, from ApplyCreature's
character branch) and the character panel's name line
(CharacterSheetProvider.BuildSheet).
Census confirmed directly against the decomp (matches AP-109's
2026-08-25 correction exactly): 11 heritages -> 17 functions. Gearknight
and Tumerok author only a MALE Get*Title function, reused for both
gender dispatch branches; Lugian authors only a FEMALE one, reused for
both. Heritage id 0xA (Penumbraen) aliases to the Shadowbound (5)
functions on both branches. Olthoi/OlthoiAcid (12/13) are excluded by
GetTitle's own unsigned range check (heritage-1) <= 0xa. Every one of
the 17 functions shares an identical unsigned rank bounds test
(rank-1) > 9 -> no title (valid range 1..10; there is no "clamp to
rank-10 title" behavior for an out-of-range rank). All 170 title
strings transcribed verbatim from the decomp, including several
PE-byte-recovered data-literal indirections in the Sho/Gearknight/
Tumerok tables ("Kou", "Ou", "Dux", "Ona", "Rea", "Tah") that match
published AC lore exactly.
GetFullName: title = GetTitle(rank, heritage, gender); when GetTitle
resolves nothing, the output is the plain name; when it resolves, the
output is "title" + a single ASCII space (PE-byte-recovered at
data_794098) + name.
Two call sites independently re-verified against the decomp for the
rank/heritage/gender property ids: CharExamineUI::SetAppraiseInfo's
local AllegianceData struct (proven by its ctor/dtor pair) never shows
an explicit field write for _rank/_hg/_gender in the decompile — a
Binary Ninja struct-flattening artifact, not a missing read — while
gmStatManagementUI::UpdateCharacterInfo shows the same three
CBaseQualities::InqInt(0x71/0xbc/0x1e) calls as plain, unambiguous
locals, confirming Gender=0x71/HeritageGroup=0xBC/AllegianceRank=0x1E
as the three inputs at both sites (ruling R8: read live off the
appraisal/qualities bundle, never RuntimeAllegianceState).
Register: AP-109 is NARROWED, not retired. Its stated risk (a ranked
character's Name line showing plain-name-only) is closed, but the same
CT4 narrowing also flagged FormatXp's non-byte-identical
GetNumberFormatA approximation as "this row's own residual sliver if
one is ever found" — that caveat is untouched by this slice and is now
the row's only surviving open item.
Tests: AllegianceRankTitleTableTests (per-function golden values,
gender-reuse rules, Penumbraen alias, Olthoi exclusion, rank/heritage/
gender bounds, GetFullName composition); AppraisalUiControllerTests
(title-bar prefix, plain-name fallback, monster-path regression pin);
CharacterSheetProviderTests (name-line prefix + plain-name fallback).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
adfce16bf1
commit
8f8c0c3a07
8 changed files with 901 additions and 24 deletions
|
|
@ -0,0 +1,248 @@
|
|||
using AcDream.App.UI.Layout;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign AS slice AS5 conformance tests for <see cref="AllegianceRankTitleTable"/>
|
||||
/// — golden values transcribed directly from <c>AllegianceSystem::GetTitle
|
||||
/// @0x005B8DD0</c>'s dispatch and its 17 <c>Get*Title</c> functions (see the
|
||||
/// class remarks there for full address citations). Gender: 1 = male,
|
||||
/// 2 = female. Heritage ids per <see cref="CharacterIdentityText.HeritageGroupDisplayName"/>:
|
||||
/// 1 Aluvian, 2 Gharu'ndim, 3 Sho, 4 Viamontian, 5 Shadowbound/Umbraen,
|
||||
/// 6 Gearknight, 7 Tumerok, 8 Lugian, 9 Empyrean, 10 (0xA) Penumbraen
|
||||
/// (Shadowbound alias), 11 (0xB) Undead, 12/13 Olthoi/OlthoiAcid (excluded).
|
||||
/// </summary>
|
||||
public sealed class AllegianceRankTitleTableTests
|
||||
{
|
||||
// ── Per-function spot checks: first / a middle / last rank each. ───────
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Yeoman")]
|
||||
[InlineData(5, "Thane")]
|
||||
[InlineData(10, "High King")]
|
||||
public void AluvianMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 1, gender: 1));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Yeoman")]
|
||||
[InlineData(3, "Baroness")] // diverges from male's "Baron" at rank 3
|
||||
[InlineData(10, "High Queen")]
|
||||
public void AluvianFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 1, gender: 2));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Sayyid")]
|
||||
[InlineData(5, "Naquib")]
|
||||
[InlineData(10, "Sultan")]
|
||||
public void GharundimMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 2, gender: 1));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Sayyida")]
|
||||
[InlineData(5, "Naquiba")]
|
||||
[InlineData(10, "Sultana")]
|
||||
public void GharundimFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 2, gender: 2));
|
||||
|
||||
/// <summary>Ranks 7 and 9 resolve through PE-byte-recovered data-literal
|
||||
/// indirections in the decomp ("Kou", "Ou") — spot-checked explicitly,
|
||||
/// not just the first/last ranks.</summary>
|
||||
[Theory]
|
||||
[InlineData(1, "Jinin")]
|
||||
[InlineData(5, "Ta-chueh")]
|
||||
[InlineData(7, "Kou")]
|
||||
[InlineData(9, "Ou")]
|
||||
[InlineData(10, "Koutei")]
|
||||
public void ShoMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 3, gender: 1));
|
||||
|
||||
/// <summary>Rank 9 diverges from the male table ("Jo-ou" vs "Ou"); rank 7
|
||||
/// shares the male table's "Kou" indirection.</summary>
|
||||
[Theory]
|
||||
[InlineData(1, "Jinin")]
|
||||
[InlineData(7, "Kou")]
|
||||
[InlineData(9, "Jo-ou")]
|
||||
[InlineData(10, "Koutei")]
|
||||
public void ShoFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 3, gender: 2));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Squire")]
|
||||
[InlineData(5, "Count")]
|
||||
[InlineData(10, "High King")]
|
||||
public void ViamontianMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 4, gender: 1));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Dame")]
|
||||
[InlineData(5, "Countess")]
|
||||
[InlineData(10, "High Queen")]
|
||||
public void ViamontianFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 4, gender: 2));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Tenebrous")]
|
||||
[InlineData(5, "Void Knight")]
|
||||
[InlineData(10, "King")]
|
||||
public void ShadowboundMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 5, gender: 1));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Tenebrous")]
|
||||
[InlineData(6, "Void Lady")] // diverges from male's "Void Lord" at rank 6
|
||||
[InlineData(10, "Queen")]
|
||||
public void ShadowboundFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 5, gender: 2));
|
||||
|
||||
/// <summary>Heritage id 0xA (Penumbraen) ALIASES to the Shadowbound
|
||||
/// functions on both gender dispatch branches — identical results to
|
||||
/// heritage 5.</summary>
|
||||
[Theory]
|
||||
[InlineData(1, 1, "Tenebrous")]
|
||||
[InlineData(2, 1, "Tenebrous")]
|
||||
[InlineData(1, 6, "Void Lord")]
|
||||
[InlineData(2, 6, "Void Lady")]
|
||||
public void Penumbraen_AliasesShadowbound(int gender, int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 0xA, gender: gender));
|
||||
|
||||
[Fact]
|
||||
public void Penumbraen_MatchesShadowboundExactlyAcrossAllRanksAndGenders()
|
||||
{
|
||||
for (int gender = 1; gender <= 2; gender++)
|
||||
for (int rank = 1; rank <= 10; rank++)
|
||||
{
|
||||
Assert.Equal(
|
||||
AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 5, gender),
|
||||
AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 0xA, gender));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gearknight authors only a MALE <c>Get*Title</c> function;
|
||||
/// <see cref="AllegianceRankTitleTable.GetTitle"/> dispatches BOTH gender
|
||||
/// branches to it for heritage 6.</summary>
|
||||
[Theory]
|
||||
[InlineData(1, "Tribunus")]
|
||||
[InlineData(5, "Principes")]
|
||||
[InlineData(8, "Dux")] // PE-byte-recovered data-literal indirection
|
||||
[InlineData(10, "Primus")]
|
||||
public void Gearknight_MaleFunctionReusedForBothGenders(int rank, string expected)
|
||||
{
|
||||
Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 6, gender: 1));
|
||||
Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 6, gender: 2));
|
||||
}
|
||||
|
||||
/// <summary>Tumerok authors only a MALE <c>Get*Title</c> function;
|
||||
/// dispatched for both genders on heritage 7.</summary>
|
||||
[Theory]
|
||||
[InlineData(1, "Xutua")]
|
||||
[InlineData(3, "Ona")] // PE-byte-recovered data-literal indirection
|
||||
[InlineData(6, "Rea")] // PE-byte-recovered data-literal indirection
|
||||
[InlineData(10, "Tah")] // PE-byte-recovered data-literal indirection
|
||||
public void Tumerok_MaleFunctionReusedForBothGenders(int rank, string expected)
|
||||
{
|
||||
Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 7, gender: 1));
|
||||
Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 7, gender: 2));
|
||||
}
|
||||
|
||||
/// <summary>Lugian authors only a FEMALE <c>Get*Title</c> function;
|
||||
/// dispatched for both genders on heritage 8.</summary>
|
||||
[Theory]
|
||||
[InlineData(1, "Laigus")]
|
||||
[InlineData(5, "Obeloth")]
|
||||
[InlineData(10, "Tiatus")]
|
||||
public void Lugian_FemaleFunctionReusedForBothGenders(int rank, string expected)
|
||||
{
|
||||
Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 8, gender: 1));
|
||||
Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 8, gender: 2));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Ensign")]
|
||||
[InlineData(5, "Captain")]
|
||||
[InlineData(10, "Aulin")]
|
||||
public void EmpyreanMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 9, gender: 1));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Ensign")]
|
||||
[InlineData(9, "Ipharsia")] // diverges from male's "Ipharsin"
|
||||
[InlineData(10, "Aulia")]
|
||||
public void EmpyreanFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 9, gender: 2));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Neophyte")]
|
||||
[InlineData(7, "Count")]
|
||||
[InlineData(8, "Viscount")]
|
||||
[InlineData(10, "Annointed")]
|
||||
public void UndeadMale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 0xB, gender: 1));
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Neophyte")]
|
||||
[InlineData(7, "Countess")] // diverges from male's "Count"
|
||||
[InlineData(8, "Viscountess")] // diverges from male's "Viscount"
|
||||
[InlineData(10, "Annointed")]
|
||||
public void UndeadFemale_MatchesDecomp(int rank, string expected)
|
||||
=> Assert.Equal(expected, AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 0xB, gender: 2));
|
||||
|
||||
// ── Dispatch bounds and exclusions. ─────────────────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData(12)] // Olthoi
|
||||
[InlineData(13)] // OlthoiAcid
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
public void Heritage_OutsideRange_ReturnsNullForBothGenders(int heritageGroup)
|
||||
{
|
||||
Assert.Null(AllegianceRankTitleTable.GetTitle(rank: 1, heritageGroup, gender: 1));
|
||||
Assert.Null(AllegianceRankTitleTable.GetTitle(rank: 1, heritageGroup, gender: 2));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(3)]
|
||||
[InlineData(-1)]
|
||||
public void Gender_Unrecognized_ReturnsNull(int gender)
|
||||
=> Assert.Null(AllegianceRankTitleTable.GetTitle(rank: 1, heritageGroup: 1, gender));
|
||||
|
||||
/// <summary>Every one of the 17 functions shares retail's identical
|
||||
/// unsigned bounds test <c>(rank - 1) > 9</c> — rank 0 (wraps huge) and
|
||||
/// any rank > 10 both resolve to "no title", uniformly.</summary>
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(11)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(int.MaxValue)]
|
||||
public void Rank_OutsideOneToTen_ReturnsNull(int rank)
|
||||
=> Assert.Null(AllegianceRankTitleTable.GetTitle(rank, heritageGroup: 1, gender: 1));
|
||||
|
||||
// ── AllegianceData::GetFullName composition. ────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ComposeFullName_ValidRank_PrefixesTitleWithSingleSpace()
|
||||
{
|
||||
string result = AllegianceRankTitleTable.ComposeFullName(
|
||||
rank: 3, heritageGroup: 1, gender: 2, name: "Aluvia");
|
||||
Assert.Equal("Baroness Aluvia", result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)] // rank absent / zero
|
||||
[InlineData(11)] // rank out of range
|
||||
public void ComposeFullName_NoTitleResolved_ReturnsPlainNameUnmodified(int rank)
|
||||
{
|
||||
string result = AllegianceRankTitleTable.ComposeFullName(
|
||||
rank, heritageGroup: 1, gender: 1, name: "Somebody");
|
||||
Assert.Equal("Somebody", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComposeFullName_UnrecognizedHeritage_ReturnsPlainName()
|
||||
{
|
||||
string result = AllegianceRankTitleTable.ComposeFullName(
|
||||
rank: 5, heritageGroup: 12 /* Olthoi */, gender: 1, name: "Xarabydun");
|
||||
Assert.Equal("Xarabydun", result);
|
||||
}
|
||||
}
|
||||
|
|
@ -459,6 +459,115 @@ public sealed class AppraisalUiControllerTests
|
|||
Assert.Equal("The Empire", HeaderText(layout, 0x1000053Au));
|
||||
}
|
||||
|
||||
// ── Campaign AS slice AS5: title-bar allegiance rank-title prefix ─────
|
||||
// Ground truth: docs/research/2026-08-25-campaign-as-ground-truth.md
|
||||
// §2a's title-bar row, gap G9, ruling R8. Retires register row AP-109.
|
||||
|
||||
[Fact]
|
||||
public void CharacterResponse_TitleBarPrefixesAllegianceRankTitle()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadExamination();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = ObjectId,
|
||||
Name = "Dww",
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
using var interaction = NewInteraction(objects, []);
|
||||
using AppraisalUiController controller = Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
new CombatState(),
|
||||
[],
|
||||
[],
|
||||
() => { },
|
||||
() => { })!;
|
||||
|
||||
interaction.ExamineSelectedOrEnterMode(ObjectId);
|
||||
var properties = new PropertyBundle();
|
||||
properties.Strings[5u] = "Template"; // Character-view marker
|
||||
properties.Ints[0x1Eu] = 3; // AllegianceRank
|
||||
properties.Ints[0xBCu] = 1; // HeritageGroup: Aluvian
|
||||
properties.Ints[0x71u] = 2; // Gender: Female
|
||||
|
||||
Assert.True(controller.Apply(Parsed(properties, MinimalCreatureProfile())));
|
||||
Assert.Equal(AppraisalView.Character, controller.ActiveView);
|
||||
// Aluvian female rank 3 = "Baroness" (AllegianceRankTitleTableTests
|
||||
// pins the table itself); GetFullName's single-space separator.
|
||||
Assert.Equal("Baroness Dww", HeaderText(layout, AppraisalUiController.TitleId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CharacterResponse_TitleBarPlainNameWhenRankAbsent()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadExamination();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = ObjectId,
|
||||
Name = "Dww",
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
using var interaction = NewInteraction(objects, []);
|
||||
using AppraisalUiController controller = Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
new CombatState(),
|
||||
[],
|
||||
[],
|
||||
() => { },
|
||||
() => { })!;
|
||||
|
||||
interaction.ExamineSelectedOrEnterMode(ObjectId);
|
||||
var properties = new PropertyBundle();
|
||||
properties.Strings[5u] = "Template"; // Character-view marker, no AllegianceRank.
|
||||
|
||||
Assert.True(controller.Apply(Parsed(properties, MinimalCreatureProfile())));
|
||||
Assert.Equal(AppraisalView.Character, controller.ActiveView);
|
||||
Assert.Equal("Dww", HeaderText(layout, AppraisalUiController.TitleId));
|
||||
}
|
||||
|
||||
/// <summary>Regression pin: <c>CreatureExamineUI</c> (monsters) never
|
||||
/// calls <c>AllegianceData::GetFullName</c> — the overwrite is scoped to
|
||||
/// the <c>character</c> branch of <c>ApplyCreature</c> only, even when
|
||||
/// rank/heritage/gender-shaped properties happen to be present.</summary>
|
||||
[Fact]
|
||||
public void CreatureResponse_TitleBarNeverGetsAllegianceRankPrefix()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadExamination();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = ObjectId,
|
||||
Name = "Drudge",
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
using var interaction = NewInteraction(objects, []);
|
||||
using AppraisalUiController controller = Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
new CombatState(),
|
||||
[],
|
||||
[],
|
||||
() => { },
|
||||
() => { })!;
|
||||
|
||||
interaction.ExamineSelectedOrEnterMode(ObjectId);
|
||||
var properties = new PropertyBundle();
|
||||
// No String 5 / Int 0x105 -> monster path, NOT the character view.
|
||||
properties.Ints[0x1Eu] = 3;
|
||||
properties.Ints[0xBCu] = 1;
|
||||
properties.Ints[0x71u] = 2;
|
||||
|
||||
Assert.True(controller.Apply(Parsed(properties, MinimalCreatureProfile())));
|
||||
Assert.Equal(AppraisalView.Creature, controller.ActiveView);
|
||||
Assert.Equal("Drudge", HeaderText(layout, AppraisalUiController.TitleId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CharacterResponse_HeritageFallsBackToCreatureTypeWhenGroupIsZero()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -104,6 +104,36 @@ public sealed class CharacterSheetProviderTests
|
|||
Assert.Equal(90L, sheet.AttributeRaise10Costs[0]);
|
||||
}
|
||||
|
||||
// ── Campaign AS slice AS5: name-line allegiance rank-title prefix ──────
|
||||
// Retires register row AP-109. Same GetFullName port + rank-property id
|
||||
// (props.GetInt(0x1E)) as the examination window's title bar
|
||||
// (AppraisalUiControllerTests.CharacterResponse_TitleBarPrefixes*).
|
||||
|
||||
[Fact]
|
||||
public void BuildSheet_WithAllegianceRank_PrefixesNameWithRankTitle()
|
||||
{
|
||||
var h = new Harness();
|
||||
var player = h.AddPlayerObject();
|
||||
player.Properties.Ints[0x1Eu] = 3; // AllegianceRank
|
||||
player.Properties.Ints[0xBCu] = 1; // HeritageGroup: Aluvian
|
||||
player.Properties.Ints[0x71u] = 2; // Gender: Female
|
||||
|
||||
var sheet = h.Provider.BuildSheet();
|
||||
|
||||
Assert.Equal("Baroness Testy", sheet.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildSheet_WithoutAllegianceRank_NameStaysPlain()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.AddPlayerObject();
|
||||
|
||||
var sheet = h.Provider.BuildSheet();
|
||||
|
||||
Assert.Equal("Testy", sheet.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildSheet_AfterLiveInt64Updates_RefreshesBothXpWindowsAndMeter()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue