Document PAK v2 resource closeout
Some checks failed
CI / linux-portable (push) Successful in 3m34s
CI / windows-gate (push) Failing after 6m5s
CI / release (push) Has been skipped

This commit is contained in:
Erik 2026-08-27 20:29:50 +02:00
parent d8c794a478
commit cccae4169a
5 changed files with 98 additions and 32 deletions

View file

@ -97,8 +97,8 @@ missing.
- A machine-local `acdream.pak` built from those DATs
- A running ACE server for connected play; the examples use
`127.0.0.1:9000`
- For the graphical client, a driver exposing the mandatory modern OpenGL
capabilities
- For the graphical client, a driver exposing the mandatory Vulkan
capabilities validated at startup
The project does not distribute Microsoft/Turbine DAT files or derived
prepared packages.
@ -111,10 +111,9 @@ dotnet build AcDream.slnx -c Release
dotnet test AcDream.slnx -c Release --no-build
```
The current baseline is a successful Release build with **8,826 passing tests
and 5 intentional skips**. The build currently reports 17 test-project
warnings tracked by [`#228`](docs/ISSUES.md#228--clean-release-build-emits-17-test-project-warnings);
production compilation has zero errors.
The current CI-filtered Windows baseline is a successful Release build with
**16,151 passing tests and zero failures**; opt-in live, installed-DAT,
prepared-package, manual, timing, and platform-specific lanes run separately.
## Prepare content
@ -127,8 +126,9 @@ dotnet run --project src\AcDream.Bake\AcDream.Bake.csproj -c Release -- `
--out "C:\Games\Asheron's Call\acdream.pak"
```
A complete package is approximately 30 GB. It is machine-local and must not be
committed. `ACDREAM_PAK_PATH` overrides the default
A complete format-2 package from the standard installed DAT set is about
570 MiB (the former format-1 package was about 30 GB). It is machine-local and
must not be committed. `ACDREAM_PAK_PATH` overrides the default
`<DAT directory>\acdream.pak`.
## Run the graphical client

View file

@ -450,6 +450,15 @@ src/
Prepared-content launch contract
-> `install.json` remains the strict backward-compatible base-pak authority
-> format 2 / recipe 6 is the current full-package contract: globally
shared texture payloads, independent adaptive blob compression, and
source-native BC1/2/3 for surfaces that require no pixel-local edit
-> format changes always use an explicit confirmed full rebuild; the
launcher preflights 2 GiB free space, shows long-work progress, validates
the candidate beside the active package, and promotes atomically
-> the prepared texture LRU is bounded to 64 MiB / 1,024 entries; GPU atlas
ownership remains in App and the whole-file mmap is virtual, not a
committed-copy cache
-> optional `pak/content.current.json` binds one cumulative overlay to the
base SHA; there is never an unbounded overlay chain
-> `pak/content.client-pending` prevents newly migrated content from

View file

@ -22,7 +22,7 @@ particles), and uses the same Silk.NET + .NET stack we target.
our tree (see CLAUDE.md for the full breakdown):
- `src/AcDream.Core/Rendering/Wb/` — pure helpers (no GL): `TerrainUtils`,
`TerrainEntry`, `RegionInfo`, `SceneryHelpers`, `TextureHelpers`.
- `src/AcDream.App/Rendering/Wb/`GL infrastructure + mesh pipeline:
- `src/AcDream.App/Rendering/Wb/`Vulkan/RHI infrastructure + mesh pipeline:
`ObjectMeshManager`, `WbMeshAdapter`, `WbDrawDispatcher`, texture cache,
shader infra, EnvCell/portal/scenery/terrain-blending pipeline classes.
@ -42,7 +42,7 @@ non-render content plus explicit bake/equivalence tooling.
**MP1a (2026-07-05): CPU mesh-extraction half moved to `AcDream.Content`.**
The GL-free portion of the former `ObjectMeshManager` — dat read → polygon
walk → vertex/index build → inline BCn/palette texture decode →
walk → vertex/index build → palette/conditional BCn texture decode →
`ObjectMeshData` — is now `MeshExtractor` in a new `src/AcDream.Content/`
assembly (no Silk.NET dependency), so the MP1b bake tool can run the exact
same extraction code offline without an OpenGL context. This was a
@ -84,7 +84,7 @@ behavior change, no divergence-register row.
survive, as they always did). The MP1b bake tool passes its own
collector.
- **Stays in `src/AcDream.App/Rendering/Wb/`:** `ObjectMeshManager` (the
staged-queue/worker-pool/Dispose-quiesce lifecycle and all GL upload;
staged-queue/worker-pool/Dispose-quiesce lifecycle and all Vulkan/RHI upload;
production workers now consume `IPreparedAssetSource`),
`ObjectRenderData`/`ObjectRenderBatch`
(hold a GL `TextureAtlasManager` field), `TextureAtlasManager`,
@ -120,9 +120,8 @@ EnvCell, Surface, palette, and texture graphs during portals. The validated
machine-local `acdream.pak` is opened through Content's
`IPreparedAssetSource`; typed GfxObj and EnvCell requests deserialize immutable
`ObjectMeshData` while retaining the existing App worker, staging, render-thread
upload, cache, ownership, and shutdown contracts. The original
format-1/bake-tool-3 render payload persists exact batch translucency so App
does not reconstruct a
upload, cache, ownership, and shutdown contracts. The prepared render payload
persists exact batch translucency so App does not reconstruct a
`GfxObjMesh` for metadata. Setup activation uses the package TOC as an explicit
type-presence index before reading valid Setup records through the bounded DAT
cache. `DatPreparedAssetSource` and `MeshExtractor` remain explicit
@ -146,8 +145,25 @@ keeps either result non-launchable until the matching client is confirmed,
including across a crash/restart. Design and gates:
`docs/plans/2026-08-25-launcher-content-stabilization.md`.
**Slice I3 prepared collision extension (2026-07-25).** The package remains
format 1 and retains mesh type values 13; bake-tool 4 appends typed GfxObj,
**PAK v2 resource closeout (2026-08-27).** Format 2 / bake recipe 6 retains
the fixed header and sorted random-access TOC while moving texture arrays into
one globally shared type-8 payload partition and independently applying
adaptive Brotli compression with raw fallback. Unedited DXT1/3/5 surfaces
retain exact DAT BC blocks through Vulkan upload; clip maps and authored
translucency continue through the RGBA edit path. The reader bounds decoded
blobs and retains shared texture arrays in a 64 MiB / 1,024-entry LRU. The
complete installed package is 597,229,424 bytes versus 29,908,271,024 bytes
for format 1; four- and nine-worker bakes have identical SHA-256. The
authoritative connected before/after route reduced heavy-route working set by
48.0%, private bytes by 26.1%, and prepared-mesh GPU bytes by 25.9% with equal
or faster matching reveal/frame percentiles. Format changes remain mandatory
launcher-confirmed full rebuilds with beside-active validation and atomic
promotion; overlays never cross a format boundary. Design and evidence:
`docs/plans/2026-08-27-pak-v2-resource-campaign.md`.
**Slice I3 prepared collision extension (2026-07-25).** At its introduction,
the package remained format 1 and retained mesh type values 13; bake-tool 4
appended typed GfxObj,
Setup, CellStruct, and EnvCell-topology collision payloads. Core owns the
immutable flat records and deterministic raw-DAT flattener. Content owns the
strict little-endian codec and `IPreparedCollisionSource`.

View file

@ -11,8 +11,9 @@ Ship one crash-safe prepared-asset format migration that:
- preserves decoded geometry, material metadata, texture bytes, deterministic
baking, corruption isolation, and random-access loading;
- does not regress cold or warm world-reveal latency or frame-time percentiles;
- reduces bake CPU/memory and client retained texture memory where the data
permits it, without changing the rendered result;
- reduces offline bake time and live client CPU/GPU memory where the data
permits it, without changing the rendered result; the one-time bake remains
bounded for the user-confirmed 16-32 GiB target machines;
- gives launcher users a clear one-time update message and progress, while
retaining the last verified package until the replacement is validated;
- passes two different worker-count bakes with identical SHA-256, the complete
@ -51,9 +52,9 @@ The 64-byte header and 24-byte sorted TOC row remain fixed. Format version is
their own exact dimensions, format, upload metadata, surface identity,
translucency, culling, and index data.
2. Texture payload keys are the first 56 bits of SHA-256 under the type-8
namespace. The writer retains and compares the full bytes for the complete
bake, making even a truncated-hash collision a loud bake failure rather
than silent substitution.
namespace. The writer retains the full SHA-256 digest and length for every
unique texture, making even a truncated-key collision a loud bake failure
rather than silent substitution without pinning another copy of all bytes.
3. Every physical blob is independently encoded. The high bit of the TOC
length marks compression; the low 31 bits are the stored length. A
compressed blob contains a four-byte decoded-length prefix followed by
@ -61,10 +62,15 @@ The 64-byte header and 24-byte sorted TOC row remain fixed. Format version is
CRC-32 covers stored bytes, then decompression is independently validated.
4. Random access remains one binary search plus one mmap copy for raw blobs.
Compressed blobs add decompression only when the writer proved a material
size win. Texture payloads use a bounded, thread-safe 256 MiB / 2,048-entry
size win. Texture payloads use a bounded, thread-safe 64 MiB / 1,024-entry
LRU; concurrently decoded meshes converge on one shared array instance.
5. Whole-file compression is forbidden. It would destroy random access and
make a small world reveal depend on unrelated content.
6. Unedited DAT DXT1/3/5 surfaces retain their exact BC1/2/3 source blocks
through bake, mmap, and Vulkan upload. Clip maps and surfaces with authored
translucency still decode to RGBA8 because their per-surface alpha edits
require pixels. This is smaller and closer to retail's hardware DXT path
than the former unconditional software decode.
## Determinism and publication
@ -79,7 +85,7 @@ DAT iterations, TOC counts, size, completion protocol, and SHA-256, then uses
the existing atomic promotion/backup transaction. Cancellation or failure
keeps the verified format-1 package. No overlay may cross this format change.
## Checkpoint evidence
## Complete installed-DAT evidence
The first installed-DAT mixed sample (four GfxObj, three Setup, three EnvCell,
all corresponding collision/topology payloads) produced 58 keys, 29 globally
@ -88,9 +94,42 @@ deduplicated texture payloads, and 57 physical blobs. Decoded payload was
and three-worker bakes had the identical SHA-256
`78886DFA28A3EDF9368A1E25C9B02A3B69ADC5DFCC01358D64A073B549B5B532`.
This sample is a checkpoint only. The size and performance release gates are
decided by the complete installed-DAT package and connected world-loading
measurements.
The final complete four-worker and nine-worker bakes are byte-identical:
- 2,237,866 logical keys and 786,892 physical blobs;
- 5,696 globally shared texture payloads;
- zero extraction/validation failures;
- 597,229,424 bytes (569.6 MiB), down 98.0% from 29,908,271,024 bytes;
- SHA-256
`37BC0EA1778F899AF9E3B2397937D373F69D615B15D3E32041BF389D93B624BF`;
- 28.7 seconds for the final four-worker bake versus 79.1 seconds for the
same-machine recipe-5 format-1 baseline (63.7% faster).
The exact before/after connected lifecycle route used the same machine,
server, 1280x720 Vulkan presentation, retail render pack, and matching client
code. Both runs passed fresh login, multi-world portal travel, same-location
revisit, fresh-process reconnect, and graceful teardown.
| Matching live measurement | Format 1 | Format 2 | Change |
|---|---:|---:|---:|
| Heavy-route final working set | 2,621.9 MiB | 1,363.9 MiB | -48.0% |
| Heavy-route final private bytes | 2,470.5 MiB | 1,825.6 MiB | -26.1% |
| Holtburg prepared-mesh GPU bytes | 229.6 MiB | 170.1 MiB | -25.9% |
| Fresh reconnect working set | 974.1 MiB | 869.0 MiB | -10.8% |
| Fresh reconnect private bytes | 1,253.8 MiB | 1,166.6 MiB | -7.0% |
| Fresh reconnect to checkpoint | 59.3 s | 59.3 s | equal |
Rynthid and Facility transition times were equal; Holtburg completed 2.9 s
faster; Aerlinthe revisit differed by 0.1 s. Matching stable CPU p50/p95 rows
were equal or slightly faster and GPU time remained within 0.1 ms. The first
raw login samples were excluded from comparison because the server started
them in different cells with 6,671 versus 11,799 world entities. Matching
screenshots preserve geometry, materials, lighting, transparency, texture
detail, and mip behavior.
Automated gates at this checkpoint: 181/181 installed-DAT Content tests,
16,151/16,151 CI-filtered Windows tests, Release build with zero warnings, and
the authoritative connected lifecycle/reconnect gate all pass.
## Work ledger
@ -100,10 +139,11 @@ measurements.
determinism.
- [x] Integrate format-2 accounting and strict validation into the bake.
- [x] Publish the recipe-6 mandatory full-rebuild launcher migration.
- [ ] Add launcher disk-space preflight and explicit long-work detail.
- [ ] Complete installed-DAT equivalence and dual-worker full bakes.
- [ ] Measure/tune package size, bake CPU/memory, read CPU/allocations, cold
- [x] Add launcher disk-space preflight and explicit long-work detail.
- [x] Complete installed-DAT equivalence and dual-worker full bakes.
- [x] Measure/tune package size, bake time, read CPU/allocations, cold
and warm reveal latency, and frame-time percentiles.
- [ ] Evaluate source-native BC texture retention only if it remains visually
- [x] Evaluate source-native BC texture retention only if it remains visually
exact and does not shift mip-generation work into the reveal frame.
- [ ] Pass complete tests, Windows CI, merge, push, and release gates.
- [x] Pass complete local tests and authoritative connected gates.
- [ ] Pass Gitea Windows CI, merge, push, and release gates.

View file

@ -30,7 +30,8 @@ public static class PakFormat {
/// 8 polygons all NoPos + Base1Solid, extracted to a 0-vertex mesh). The
/// binary format remains version 1. Version 6 introduces pak format 2:
/// globally shared texture payloads plus independently Brotli-compressed
/// blobs with raw fallback. Mesh geometry and decoded pixels remain exact.
/// blobs with raw fallback. Mesh geometry and source texture bytes remain
/// exact; unedited DXT surfaces now retain their native BC blocks.
/// </summary>
public const uint CurrentBakeToolVersion = 6;
}