feat(pathfinding): .swb uniform-chunk elision (format v5) — Trammel 592→232 MB (#2465)
## Summary Sub-project #1 of the `.swb` step-cache size-reduction roadmap (`dev-docs/pathfinding.md` § Future work). Adds **uniform-chunk elision** to the `StepCacheFile` format, bumping it **v4 → v5**. A fully-uniform 16×16 chunk — no strata, **no swim layer**, all 19 base arrays constant (open ocean, Green Acres, void) — serializes to a **~28-byte record** (`KindUniform`) instead of ~5,393, and reconstructs **byte-identically** via `Array.Fill`. Non-uniform chunks use the existing v4 body (`KindFull`) with the swim-layer and strata trailers **fully preserved** — the Kind byte is just prepended. ## Calibrated result (measured, not projected) Baked Trammel via `SaveToFile`: | | | |---|---:| | Chunks | 114,688 | | Uniform (swim-aware) → elided | 62.7% | | Swim-layer chunks (stay Full) | 8.9% | | Strata chunks (stay Full) | 1.8% | | Baseline (full records) | 592.2 MB | | **Actual v5 `.swb`** | **231.9 MB (−61%)** | The residual is ~150 MB of non-uniform land Z-blocks (targeted by #2 predictive-Z) + ~81 MB of swim-layer trailers (#2/#3). #2 and #3 are separate follow-up PRs. ## Implementation - `StepChunk.IsUniform()` — false if it has strata **or a swim layer**, else true only when all 19 base arrays are constant (the "all-same" check uses the SIMD-accelerated `ContainsAnyExcept`). - `StepCacheFile` v5 — `Kind` byte (`KindFull=0`/`KindUniform=2`, 1 reserved); uniform write/read; `FormatVersion`/`MinSupportedVersion` → 5 (v4 files rejected on open and re-baked). No `StepCache`/algorithm/index changes; fingerprint logic untouched. ## Tests 7 `StepCacheFileV5Tests` (uniform round-trip + `<200 B` compactness, varied-full, swim-layer-full, strata-full, swim+strata combined, v4 version-gate rejection) + the existing StepCache/pathfinding suite — **70 pass**, including the prior `SwimLayer_RoundTrips`. An independent review verified write/read symmetry, cast round-tripping, swim/strata preservation, and the version gate (READY TO MERGE).
This commit is contained in:
parent
47bf2d1f13
commit
c7697e1dc5
4 changed files with 324 additions and 9 deletions
|
|
@ -14,11 +14,11 @@ namespace Server.Engines.Pathing.Cache;
|
|||
/// only when the cache asks for them. RAM stays bounded by MaxResidentChunks regardless
|
||||
/// of file size.
|
||||
///
|
||||
/// File layout v3 (little-endian, BufferWriter / BufferReader convention):
|
||||
/// File layout v5 (little-endian, BufferWriter / BufferReader convention):
|
||||
///
|
||||
/// Header (48 bytes):
|
||||
/// u32 Magic = 0x42575300 ('SWB\0')
|
||||
/// u32 Version = current FormatVersion (3)
|
||||
/// u32 Version = current FormatVersion (5)
|
||||
/// u32 MapId
|
||||
/// u64 Fingerprint XxHash3 over (1) LandTable + ItemTable flags AND (2) the
|
||||
/// on-disk bytes of mapX.mul / .uop, staidxX.mul, staticsX.mul.
|
||||
|
|
@ -34,6 +34,10 @@ namespace Server.Engines.Pathing.Cache;
|
|||
/// u16 ChunkX
|
||||
/// u16 ChunkY
|
||||
/// u32 BuiltMultisVersion
|
||||
/// u8 Kind 0 = Full; 2 = Uniform
|
||||
/// // Uniform (Kind == 2): ~28-byte record — all 256 cells share these single values:
|
||||
/// byte walkMask, wetMask; sbyte sourceZ; sbyte walkZ_N..NW (8); sbyte swimZ_N..NW (8)
|
||||
/// // Full (Kind == 0) body:
|
||||
/// u8 HasStrata 0 = single-Z chunk (no strata trailer); 1 = strata trailer follows
|
||||
/// u8 HasSwimLayer 0 = no shore cells (no swim trailer); 1 = swim trailer follows
|
||||
/// byte WalkMask[256]
|
||||
|
|
@ -68,7 +72,7 @@ namespace Server.Engines.Pathing.Cache;
|
|||
internal static class StepCacheFile
|
||||
{
|
||||
public const uint Magic = 0x42575300; // 'SWB\0'
|
||||
public const uint FormatVersion = 4;
|
||||
public const uint FormatVersion = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Lowest format version this binary can load. Files below this version are treated as
|
||||
|
|
@ -79,9 +83,16 @@ internal static class StepCacheFile
|
|||
/// static-over-land surfaces (sewer/dungeon walkways, bridges, upper building floors),
|
||||
/// producing ~98% source-Z fallthroughs on those routes. The on-disk layout is
|
||||
/// unchanged; only the strata population differs, so the bump exists purely to force a
|
||||
/// one-time re-bake of stale v3 files on first boot under the new binary.
|
||||
/// one-time re-bake of stale v3 files on first boot under the new binary. Bumped to 5 for
|
||||
/// uniform-chunk elision: each record now begins with a Kind byte (0 = Full, 2 = Uniform);
|
||||
/// a fully-uniform chunk (no strata, no swim layer, all 19 base arrays constant) stores
|
||||
/// one cell's worth of data (~28 bytes) instead of the full record.
|
||||
/// </summary>
|
||||
public const uint MinSupportedVersion = 4;
|
||||
public const uint MinSupportedVersion = 5;
|
||||
|
||||
// Per-chunk record discriminator (first byte after BuiltMultisVersion). 1 is reserved.
|
||||
private const byte KindFull = 0;
|
||||
private const byte KindUniform = 2;
|
||||
|
||||
private const int HeaderSize =
|
||||
sizeof(uint) // Magic
|
||||
|
|
@ -100,7 +111,7 @@ internal static class StepCacheFile
|
|||
/// <summary>Fixed-size portion of a chunk record (everything except the optional strata + swim trailers).</summary>
|
||||
private const int BytesPerChunkBase =
|
||||
sizeof(ushort) + sizeof(ushort) + sizeof(uint)
|
||||
+ sizeof(byte) + sizeof(byte) // HasStrata + HasSwimLayer
|
||||
+ sizeof(byte) + sizeof(byte) + sizeof(byte) // Kind + HasStrata + HasSwimLayer
|
||||
+ StepChunk.CellsPerChunk // WalkMask
|
||||
+ StepChunk.CellsPerChunk // WetMask
|
||||
+ StepChunk.CellsPerChunk // SourceZ
|
||||
|
|
@ -367,6 +378,35 @@ internal static class StepCacheFile
|
|||
w.Write((ushort)chunkY);
|
||||
w.Write((uint)chunk.BuiltMultisVersion);
|
||||
|
||||
// Kind: 0 = Full, 2 = Uniform. A uniform chunk (no strata, no swim layer, all 19 base
|
||||
// arrays constant) stores one cell's worth of data (~28-byte record total).
|
||||
if (chunk.IsUniform())
|
||||
{
|
||||
w.Write(KindUniform);
|
||||
w.Write(chunk.WalkMask[0]);
|
||||
w.Write(chunk.WetMask[0]);
|
||||
w.Write((byte)chunk.SourceZ[0]);
|
||||
w.Write((byte)chunk.WalkZN[0]);
|
||||
w.Write((byte)chunk.WalkZNE[0]);
|
||||
w.Write((byte)chunk.WalkZE[0]);
|
||||
w.Write((byte)chunk.WalkZSE[0]);
|
||||
w.Write((byte)chunk.WalkZS[0]);
|
||||
w.Write((byte)chunk.WalkZSW[0]);
|
||||
w.Write((byte)chunk.WalkZW[0]);
|
||||
w.Write((byte)chunk.WalkZNW[0]);
|
||||
w.Write((byte)chunk.SwimZN[0]);
|
||||
w.Write((byte)chunk.SwimZNE[0]);
|
||||
w.Write((byte)chunk.SwimZE[0]);
|
||||
w.Write((byte)chunk.SwimZSE[0]);
|
||||
w.Write((byte)chunk.SwimZS[0]);
|
||||
w.Write((byte)chunk.SwimZSW[0]);
|
||||
w.Write((byte)chunk.SwimZW[0]);
|
||||
w.Write((byte)chunk.SwimZNW[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
w.Write(KindFull); // Full
|
||||
|
||||
var strataOffsetByCell = chunk.GetStrataOffsetByCellForSerialization();
|
||||
var strataData = chunk.GetStrataDataForSerialization();
|
||||
var hasStrata = strataOffsetByCell != null;
|
||||
|
|
@ -433,11 +473,37 @@ internal static class StepCacheFile
|
|||
r.ReadUShort();
|
||||
r.ReadUShort();
|
||||
var multisVersion = (int)r.ReadUInt();
|
||||
var hasStrata = r.ReadByte() != 0;
|
||||
var hasSwimLayer = r.ReadByte() != 0;
|
||||
var kind = r.ReadByte();
|
||||
|
||||
var chunk = new StepChunk { BuiltMultisVersion = multisVersion };
|
||||
|
||||
if (kind == KindUniform) // Uniform — one cell's worth of the 19 base arrays, fill all 256 cells.
|
||||
{
|
||||
Array.Fill(chunk.WalkMask, r.ReadByte());
|
||||
Array.Fill(chunk.WetMask, r.ReadByte());
|
||||
Array.Fill(chunk.SourceZ, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZN, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZNE, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZE, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZSE, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZS, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZSW, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZW, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.WalkZNW, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZN, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZNE, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZE, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZSE, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZS, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZSW, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZW, (sbyte)r.ReadByte());
|
||||
Array.Fill(chunk.SwimZNW, (sbyte)r.ReadByte());
|
||||
return chunk;
|
||||
}
|
||||
|
||||
var hasStrata = r.ReadByte() != 0;
|
||||
var hasSwimLayer = r.ReadByte() != 0;
|
||||
|
||||
r.Read(chunk.WalkMask);
|
||||
r.Read(chunk.WetMask);
|
||||
ReadSBytes(r, chunk.SourceZ);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue