feat(net): D.2b-B B-Wire — InventoryRemoveObject (0x0024) parser

Top-level GameMessage (UIQueue), not a GameEvent. Server sends when an
object leaves the client's inventory view (sold / dropped / destroyed).
Layout: opcode(4) + guid(4) = 8 bytes — the smallest inventory wire.
3 tests: happy path, wrong opcode, truncated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-21 18:52:21 +02:00
parent 87e354f583
commit 79c0374d53
2 changed files with 63 additions and 0 deletions

View file

@ -0,0 +1,31 @@
using System;
using System.Buffers.Binary;
namespace AcDream.Core.Net.Messages;
/// <summary>
/// Inbound <c>InventoryRemoveObject (0x0024)</c> — a top-level GameMessage (UIQueue),
/// NOT a GameEvent. The server tells the client an object left its inventory view
/// (given away / sold / destroyed). The client drops it from object maintenance.
///
/// <para>Wire layout (ACE <c>GameMessageInventoryRemoveObject.cs</c>, size hint 8):</para>
/// <code>
/// u32 opcode = 0x0024
/// u32 guid
/// </code>
/// </summary>
public static class InventoryRemoveObject
{
public const uint Opcode = 0x0024u;
public readonly record struct Parsed(uint Guid);
/// <summary>Parse a raw 0x0024 body. Returns null on opcode mismatch / truncation.</summary>
public static Parsed? TryParse(ReadOnlySpan<byte> body)
{
if (body.Length < 8) return null; // 4 + 4
if (BinaryPrimitives.ReadUInt32LittleEndian(body) != Opcode) return null;
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body[4..]);
return new Parsed(guid);
}
}

View file

@ -0,0 +1,32 @@
using System.Buffers.Binary;
using AcDream.Core.Net.Messages;
using Xunit;
namespace AcDream.Core.Net.Tests.Messages;
public sealed class InventoryRemoveObjectTests
{
private static byte[] Build(uint guid, uint opcode = 0x0024u)
{
var b = new byte[8];
BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(0), opcode);
BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(4), guid);
return b;
}
[Fact]
public void TryParse_valid_returnsGuid()
{
var p = InventoryRemoveObject.TryParse(Build(0x50000A07u));
Assert.NotNull(p);
Assert.Equal(0x50000A07u, p!.Value.Guid);
}
[Fact]
public void TryParse_wrongOpcode_returnsNull()
=> Assert.Null(InventoryRemoveObject.TryParse(Build(1, opcode: 0x0023u)));
[Fact]
public void TryParse_truncated_returnsNull()
=> Assert.Null(InventoryRemoveObject.TryParse(new byte[7]));
}