executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
asset-pipelineExpert game asset pipeline — asset system lifecycle, import/cook pipeline, streaming & hot-reload, reference counting, virtual filesystem, and shader pipeline management.
Independent GitHub snapshot · Automated classification · Original documentation governs
Assets are the foundation of every game: the pipeline takes 100k DCC source files and turns them into compressed, streamable, cache-friendly runtime formats — without frame spikes, without leaks, without 3-minute load screens longer than the content itself.
| Source asset | Runtime asset | |
|---|---|---|
| Where | tool-generated (Blender, Maya, Photoshop, Houdini) | shipped, loaded at runtime |
| Format | .blend, .ma, .psd, .zip ... | engine-native (.mesh, .anim, .tex, .shader) |
| Transform | DCC tools (artist-facing) | Cook step (build-time) |
| Versioning | VCS + LFS | pak/stream |
The pipeline separates them: source lives under version control; cooked assets ship. Cook = offline, deterministic conversion.
One importer per source type: FBX/glTF for meshes, PNG/TGA for textures, WAV/FLAC for audio, .asset for configs. Importers are normalizers: they read source and emit a canonical engine-format intermediate (.import file) and a preview.
struct ImportResult {
MeshSource* mesh; // canonical
MaterialDesc material;
std::vector<SubMesh> subs;
uint64_t sourceFingerprint; // content hash, not mtime
};
Fingerprint = hash of source bytes (+ importer version + import options). Only re-cook when the fingerprint changes. This makes turning an artist's "I touched every file" into "re-cook 2 files".
Runtime conversion, deterministic, offline:
| Input | Cook | Output |
|---|---|---|
| .blend mesh | weld verts, tangent calc, LOD gen, KTX2/BCn compress | .mesh (with LODs) |
| .png texture | mipgen (8–13 mips), swizzle, DXT/BC7/ETC2/BCn | .tex |
| WAV | resample, ADX/Ogg Vorbis/Opus | .audio |
| anim | compress (key-bit quantization, per-track reindex) | .anim |
| HLSL/GLSL | compile to DXIL/SPIR-V/machine blobs | .shader |
| level | bake navmesh, lightmaps, build BVH/tiles | .level |
Cook output goes to the shipping GameData (mount {GameData}, see filesystem.md).
assets.manifest (JSON/binary)
entry: { id, sourceHash, outputPath, dependencies[] }
Runtime load by id (not by path): GetAsset(AID("hero_model")) — id avoids path-sensitive bugs. The manifest also records shader variants, LOD tables, and dependency closure.
Same source → byte-identical output in a clean checkout (fixed importer versions, stable order, stable compression seeds). Without determinism, incremental builds get poisoned and QA can't trust a cached build.
class AssetManager {
// id -> live asset; refcounted; hot-reloaded and streamed
AssetHandle Load(AssetId id); // resolves manifest -> stream
void RequestStream(AssetId id); // async
void Unload(AssetId id); // decrement refcount
};
struct Asset {
AssetId id; RefCount refs; uint32_t flags;
void* data; uint64_t size;
StreamingHandle streamState;
};
Refcounting is the API: Load increments, Unload decrements. A streamed texture is loaded when first referenced, unloads when unreferenced. This keeps the live set = exactly what's in use.
Async request goes through the VFS (see filesystem.md): read async → cook-on-load (decompress, decode) on a worker → upload to GPU → mark ready. Pending loads are tracked; the frame only sees ready assets.
AsyncLoadBBC tx = AssetManager::RequestStream(id);
// later: await via the job system; or poll tx.isReady()
Two hot reload paths:
modules-and-tools.md).{EngineRoot} source dirs; on fingerprint change → re-cook → swap live GPU textures/meshes/shader blobs next frame (or next level load).Tooling uses this to close the artist iteration loop to seconds.
Assets can reference assets (material → textures, skeleton → meshes, level → prefabs). Cycles must be broken:
A leak (referenced never unloaded) shows up as: RSS growth on repeated level loads. CI runs a "load/unload 10x" leak check (see memory-tracking.md).
pak).pak files (multiple assets per archive; single handle; good compression).filesystem.md).| Priority | Asset | Stream? |
|---|---|---|
| .pak index | always loaded | no |
| level meshes/textures | first-need | yes (priority queue) |
| npc anims/audio | first-need | yes |
| skippable cinematics | pre-skip | optional |
DXIL, SPIR-V, GCN/MSL).shadercache shipped (not built on user machine).| Load pattern | Spike? | Fix |
|---|---|---|
Load on main thread in Update | yes | async + worker decode |
| Big GPU upload mid-frame | yes | staging ring + frame-deferred upload |
| First-reference shader compile | yes | cook-time compile + PSO cache |
| Texture mipgen at load | yes | mips at cook; runtime = streaming higher mips |
fread.references/import-and-cook.md — DCC import, normalization, deterministic cook, content hashing, LOD/tangent generationreferences/asset-registry.md — id registry, refcount API, streaming states, leak detectionreferences/hot-reload.md — fingerprinting, re-cook, live swaps, tool loopreferences/packing.md — pak layout, compression, streaming pools, update/delta packsreferences/shader-pipeline.md — variant management, PSO cache, compile at cook timereferences/streaming-throttle.md — priority queues, budgets, adaptive streaming, hitch prevention