refactor(net): #13 Parsed.TrailerTruncated + diag logging

Code-quality review followup on Task 2 (becbde6) — addresses I1 (the
forward-looking concern that Tasks 3-9's inner-catch will leave partial
lists visible to callers with no signal) and M1 (silent inner catch).

Changes:
  - Parsed gains a trailing `bool TrailerTruncated` field. Both
    construction sites pass `false` by default; the trailer try/catch
    flips a local `trailerTruncated` to `true` on FormatException and
    feeds it into the final return.
  - Inner catch logs `pos`/`payload.Length`/exception message under
    ACDREAM_DUMP_VITALS=1, mirroring the outer catch's diagnostic
    pattern.
  - Task 2 test strengthened to assert defaults on Options2 /
    SpellbookFilters / HotbarSpells / DesiredComps / GameplayOptions /
    Equipped + TrailerTruncated=false (M2 followup — gives Tasks 3-9
    a regression guard if they consume into the wrong field).
  - New test `TryParse_TrailerAbsent_LessThan8BytesAfterEnchantments_*`
    documents the contract that <8 bytes after enchantments means the
    trailer is absent (not truncated): TrailerTruncated stays false,
    upstream attribute data survives.
  - Plan updated in lockstep so Tasks 3-11 implementers see the
    `trailerTruncated` local and the new return-arg position.

271/271 AcDream.Core.Net.Tests pass.
This commit is contained in:
Erik 2026-05-10 08:26:08 +02:00
parent becbde60a4
commit 9a0dfe03da
3 changed files with 96 additions and 11 deletions

View file

@ -154,9 +154,17 @@ public readonly record struct Parsed(
uint SpellbookFilters,
ReadOnlyMemory<byte> GameplayOptions,
IReadOnlyList<InventoryEntry> Inventory,
IReadOnlyList<EquippedEntry> Equipped);
IReadOnlyList<EquippedEntry> Equipped,
bool TrailerTruncated);
```
> **Code-review followup (added after Task 2 review):** the trailing
> `TrailerTruncated` flag was added to let callers distinguish a clean
> parse from one where the trailer try/catch swallowed a `FormatException`
> mid-section (Tasks 39 will make this reachable). All construction sites
> pass `TrailerTruncated: false` by default; the trailer try/catch in
> `TryParse` flips a local to `true` on catch.
- [ ] **Step 3: Update `BuildPartial` to fill the new fields with defaults.**
Replace the body of `BuildPartial` (~line 275) with:
@ -179,7 +187,8 @@ private static Parsed BuildPartial(
0u,
ReadOnlyMemory<byte>.Empty,
System.Array.Empty<InventoryEntry>(),
System.Array.Empty<EquippedEntry>());
System.Array.Empty<EquippedEntry>(),
TrailerTruncated: false);
}
```
@ -198,7 +207,8 @@ return new Parsed(
0u,
ReadOnlyMemory<byte>.Empty,
System.Array.Empty<InventoryEntry>(),
System.Array.Empty<EquippedEntry>());
System.Array.Empty<EquippedEntry>(),
TrailerTruncated: false);
```
- [ ] **Step 5: Run the build + existing tests to verify no regressions.**
@ -283,6 +293,7 @@ List<(uint, uint)> desiredComps = new();
ReadOnlyMemory<byte> gameplayOptions = ReadOnlyMemory<byte>.Empty;
List<InventoryEntry> inventory = new();
List<EquippedEntry> equipped = new();
bool trailerTruncated = false;
try
{
@ -292,9 +303,14 @@ try
options1 = ReadU32(payload, ref pos);
}
}
catch (FormatException)
catch (FormatException ex)
{
// Trailer corrupted — keep what we have and return.
// Trailer corrupted — keep what we have and flag it. Tasks 3-9
// can leave partial lists in scope; TrailerTruncated lets callers
// ignore the trailer when they need all-or-nothing semantics.
trailerTruncated = true;
if (System.Environment.GetEnvironmentVariable("ACDREAM_DUMP_VITALS") == "1")
System.Console.WriteLine($"PlayerDescriptionParser: trailer FormatException at pos={pos}/{payload.Length}: {ex.Message}");
}
return new Parsed(
@ -302,9 +318,14 @@ return new Parsed(
bundle, positions, attributes, skills, spells, enchantments,
optionFlags, options1, options2,
shortcuts, hotbarSpells, desiredComps, spellbookFilters,
gameplayOptions, inventory, equipped);
gameplayOptions, inventory, equipped, trailerTruncated);
```
> **Tasks 39 note:** every `return new Parsed(...)` extension or
> rewrite in subsequent tasks must include `trailerTruncated` as the
> final positional argument, and any new try-blocks that read trailer
> sections should set `trailerTruncated = true;` in their catch.
- [ ] **Step 4: Run the test — expect PASS.**
Run: `dotnet test --filter "FullyQualifiedName~TryParse_TrailerOptionFlagsAndOptions1"`