feat(D.5.4): ClientObjectTable.Ingest merge-upsert + RecordMembership
Field-level merge (retail SetWeenieDesc): create-if-absent else patch present fields, preserve PropertyBundle. Effects unconditional (D.5.2 contract). RecordMembership = PD manifest. Locks the Coldeve no-prior-stub fix + out-of-order. Renames _items→_objects throughout; Reindex stub wired (Task 6 fills it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b83f17a927
commit
d9c427cd6c
2 changed files with 171 additions and 12 deletions
|
|
@ -230,4 +230,91 @@ public sealed class ClientObjectTableTests
|
|||
Structure: null, MaxStructure: null, Workmanship: null);
|
||||
Assert.Equal(0x99u, d.ContainerId);
|
||||
}
|
||||
|
||||
private static WeenieData FullWeenie(uint guid, uint icon = 0x06001234u,
|
||||
string name = "Sword", ItemType type = ItemType.MeleeWeapon, uint effects = 0,
|
||||
int? value = 100, int? stack = 1, uint? container = null, uint wcid = 0xABCDu) =>
|
||||
new WeenieData(guid, name, type, wcid, icon, 0, 0, effects,
|
||||
value, stack, StackSizeMax: 1, Burden: 10, ContainerId: container,
|
||||
WielderId: null, ValidLocations: null, CurrentWieldedLocation: null,
|
||||
Priority: null, ItemsCapacity: null, ContainersCapacity: null,
|
||||
Structure: null, MaxStructure: null, Workmanship: null);
|
||||
|
||||
[Fact]
|
||||
public void Ingest_NewItemWithNoPriorStub_Creates_AndFiresAdded() // the Coldeve bug
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
ClientObject? added = null;
|
||||
table.ObjectAdded += o => added = o;
|
||||
var obj = table.Ingest(FullWeenie(0x500000B0u));
|
||||
Assert.NotNull(added);
|
||||
Assert.Equal(0x06001234u, table.Get(0x500000B0u)!.IconId);
|
||||
Assert.Equal(0xABCDu, obj.WeenieClassId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingest_Existing_PatchesInPlace_PreservesPropertyBundle()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.Ingest(FullWeenie(0x500000B1u));
|
||||
table.Get(0x500000B1u)!.Properties.Ints[999u] = 7; // simulate appraise
|
||||
ClientObject? updated = null;
|
||||
table.ObjectUpdated += o => updated = o;
|
||||
table.Ingest(FullWeenie(0x500000B1u, name: "Renamed"));
|
||||
Assert.NotNull(updated);
|
||||
Assert.Equal("Renamed", table.Get(0x500000B1u)!.Name);
|
||||
Assert.Equal(7, table.Get(0x500000B1u)!.Properties.Ints[999u]); // NOT clobbered
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingest_AbsentNullableField_DoesNotClobber()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.Ingest(FullWeenie(0x500000B2u, value: 100));
|
||||
var noValue = FullWeenie(0x500000B2u) with { Value = null };
|
||||
table.Ingest(noValue);
|
||||
Assert.Equal(100, table.Get(0x500000B2u)!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingest_Effects_AssignedUnconditionally_ClearsToZero() // D.5.2 contract
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.Ingest(FullWeenie(0x500000B3u, effects: 0x1u));
|
||||
Assert.Equal(0x1u, table.Get(0x500000B3u)!.Effects);
|
||||
table.Ingest(FullWeenie(0x500000B3u, effects: 0u));
|
||||
Assert.Equal(0u, table.Get(0x500000B3u)!.Effects);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecordMembership_CreatesEntry_AndSetsEquip()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.RecordMembership(0x500000B4u, equip: EquipMask.MeleeWeapon);
|
||||
var o = table.Get(0x500000B4u);
|
||||
Assert.NotNull(o);
|
||||
Assert.Equal(EquipMask.MeleeWeapon, o!.CurrentlyEquippedLocation);
|
||||
Assert.Equal(0u, o.IconId); // data not set — CreateObject fills it
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ingest_AfterMembership_FillsData_NoDuplicate() // out-of-order: PD then CreateObject
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.RecordMembership(0x500000B5u);
|
||||
table.Ingest(FullWeenie(0x500000B5u));
|
||||
Assert.Equal(1, table.ObjectCount);
|
||||
Assert.Equal(0x06001234u, table.Get(0x500000B5u)!.IconId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Membership_AfterIngest_NoDuplicate_PreservesData() // out-of-order: CreateObject then PD
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.Ingest(FullWeenie(0x500000B6u)); // CreateObject first (ground/vendor item)
|
||||
table.RecordMembership(0x500000B6u, equip: EquipMask.MeleeWeapon); // then PD manifest
|
||||
Assert.Equal(1, table.ObjectCount);
|
||||
Assert.Equal(0x06001234u, table.Get(0x500000B6u)!.IconId); // data NOT clobbered by membership
|
||||
Assert.Equal(EquipMask.MeleeWeapon, table.Get(0x500000B6u)!.CurrentlyEquippedLocation);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue