From 7badecf387a6aa87996569a15d4eb371810339d6 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 19:19:13 +0200 Subject: [PATCH] =?UTF-8?q?test(app):=20D.2b-B=20B-Wire=20=E2=80=94=20burd?= =?UTF-8?q?en=20wire-first=20+=20live-refresh=20tests=20+=20review=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final-review response. Adds the spec §5 / Task-16 burden end-to-end coverage the per-task plan missed: (1) burden reads wire EncumbranceVal over the carried sum (asserts 50% from wire 7500, not 20% from sum 3000 — retires AP-48's fallback as the primary), (2) a live player-int update repaints the bar (60%), which only happens via the C1d Concerns `o.ObjectId == p` branch. Both are discriminating (fail if the respective branch is reverted). Plus two clarifying comments from the review: the 0x02CD player route depends on the login PD upsert having created the player object (no-ops, no phantom, if not); and a TODO that the container-open phase must treat ViewContents as a full replace, not the additive merge used here. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.Core.Net/GameEventWiring.cs | 4 ++ src/AcDream.Core.Net/ObjectTableWiring.cs | 5 ++- .../UI/Layout/InventoryControllerTests.cs | 40 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/AcDream.Core.Net/GameEventWiring.cs b/src/AcDream.Core.Net/GameEventWiring.cs index 6676f0a1..c9aec8a5 100644 --- a/src/AcDream.Core.Net/GameEventWiring.cs +++ b/src/AcDream.Core.Net/GameEventWiring.cs @@ -253,6 +253,10 @@ public static class GameEventWiring // B-Wire: ViewContents (0x0196) — the server's full contents list for a // container you opened. Record membership for each entry so the table is // correct before the container-open UI mounts the second item list. + // TODO(container-open): ViewContents is the AUTHORITATIVE full snapshot. When the + // container-open phase consumes it, treat it as a full REPLACE (flush the container's + // prior membership first), not the additive merge below — otherwise items removed + // server-side since the last open would linger. dispatcher.Register(GameEventType.ViewContents, e => { var p = GameEvents.ParseViewContents(e.Payload.Span); diff --git a/src/AcDream.Core.Net/ObjectTableWiring.cs b/src/AcDream.Core.Net/ObjectTableWiring.cs index c94c343d..ca21c79a 100644 --- a/src/AcDream.Core.Net/ObjectTableWiring.cs +++ b/src/AcDream.Core.Net/ObjectTableWiring.cs @@ -34,7 +34,10 @@ public static class ObjectTableWiring // B-Wire: PrivateUpdatePropertyInt (0x02CD) carries no guid — it targets the // local player. Route it to the player object so live EncumbranceVal updates the - // burden bar. (No-op if the player guid isn't wired yet.) + // burden bar. The player ClientObject is created at login by the PD UpsertProperties + // call (which precedes any live 0x02CD), so UpdateIntProperty finds it. If it somehow + // hasn't yet, this no-ops (UpdateIntProperty returns false on an unknown guid) rather + // than creating a phantom — the next PD / CreateObject seeds it. session.PlayerIntPropertyUpdated += u => { if (playerGuid is not null) diff --git a/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs index 7da7e627..d1fb4746 100644 --- a/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs @@ -152,6 +152,46 @@ public class InventoryControllerTests Assert.Contains("Contents of Backpack", CaptionText(contentsCap)); } + [Fact] + public void Burden_reads_wire_EncumbranceVal_over_carried_sum() + { + // B-Wire: the burden bar must read the server's wire EncumbranceVal (PropertyInt 5), + // NOT the client-side carried-Burden sum (retires AP-48 — the sum is only the fallback). + var (layout, _, _, _, meter, burdenText, _, _) = BuildLayout(); + var objects = new ClientObjectTable(); + // Str 100 → capacity 15000. A carried item sums to 3000 (would read 20%), but the + // server says EncumbranceVal = 7500 → load 0.5 → fill 0.1667 → "50%". Wire wins. + objects.AddOrUpdate(new ClientObject { ObjectId = 0xA, ContainerId = Player, Burden = 3000 }); + var bundle = new PropertyBundle(); + bundle.Ints[5] = 7500; // EncumbranceVal (PropertyInt 5) + objects.UpsertProperties(Player, bundle); + + Bind(layout, objects, strength: 100); + + Assert.Equal(0.16667f, meter.Fill() ?? -1f, 3); // 7500/15000/3 (wire), not 3000-based 0.0667 + Assert.Contains("50%", CaptionText(burdenText)); // not "20%" + } + + [Fact] + public void Live_player_int_update_refreshes_burden() + { + // B-Wire C1d: a live PrivateUpdatePropertyInt (0x02CD) for the player's EncumbranceVal + // fires ObjectUpdated on the PLAYER object; Concerns now includes o.ObjectId == player, + // so the burden bar repaints. Without the C1d branch this update would be ignored. + var (layout, _, _, _, meter, burdenText, _, _) = BuildLayout(); + var objects = new ClientObjectTable(); + var bundle = new PropertyBundle(); + bundle.Ints[5] = 3000; // initial EncumbranceVal → load 0.2 → "20%" + objects.UpsertProperties(Player, bundle); + Bind(layout, objects, strength: 100); + Assert.Contains("20%", CaptionText(burdenText)); + + objects.UpdateIntProperty(Player, 5u, 9000); // live 0x02CD: → load 0.6 → "60%" + + Assert.Contains("60%", CaptionText(burdenText)); + Assert.Equal(0.2f, meter.Fill() ?? -1f, 3); // 9000/15000/3 + } + // Reads the text of the UiText caption child attached by the controller. private static string CaptionText(UiElement host) {