Class ContentDeclaration
What content is, expressed as rules instead of as marks on assets.
This replaces the Addressables groups and labels that used to be the declaration. The reason is not taste: a
mark is a step a human forgets, and forgetting was silent — Rock.prefab shipped unreachable for months, and
the story's props needed a helper tool (MarkPrefabsAddressable) that existed only because there was nowhere to
say this once. A rule cannot be forgotten per asset.
The rule is one sentence: content is everything under a folder named Prefabs or Animations that lives
inside a pack we ship, plus a short list of other content folders, plus anything declared explicitly. That is
a derivation, not an invention — every one of the 3941 records the marked declaration produced still resolves,
and the handful the folders do not reach are named one by one in ExplicitFileName.
Two limits in that sentence are load-bearing, and both were put there after measuring what their absence costs:
- Inside a pack we ship (GrindFest.Content.Editor.ContentDeclaration.ContentPacks). "Any folder named
Prefabsanywhere" was the first version, and it declared 18656 assets —TopDownEngine/ThirdParty/MoreMountainsdemo content, a Sci-Fi and a Kids art pack nothing in the game references, and every part ofPolygonFantasyHeroCharacters. The pack list is the set of packs the shipped catalogue actually used, so it is evidence, not preference. - Never under
Models. A pack'sModels/folder is source art — 6647.fbxfiles in this project — andGetMainAssetTypeAtPathreports an imported model as aGameObject, so a type filter cannot tell it from a prefab. The one model that genuinely shipped (FX_Ring_Mesh.fbx) is declared explicitly.
Two more pieces of information the old declaration carried, and where they come from now:
- Kind (monster/item/room/…) decides which assets become definitions with encoded components. It comes
from a folder table, because that is what the data says: monsters live in
Prefabs/Characters, items inPrefabs/Items, rooms inDungeons/Fields. A folder with no kind still contributes a catalogue entry — being resolvable by name is a different fact from being a definable thing. - Tier (when it loads) is inferred from the path and the kind, not authored.
What is deliberately absent: any notion of a bundle, a group, a label set, or a download. That was transport. The declaration says what exists; where it ships is a build decision.
public static class ContentDeclaration
- Inheritance
-
objectContentDeclaration
Fields
- ExplicitFileName
Where the explicit list lives. Hand-editable, which is the point.
Properties
Methods
- AuthoringComponentOn(string)
The type name of the first generation component on a prefab, or null.
A prefab reports
GameObject, so the type chain cannot tell that the prefab is really an authoring object:Barrels.prefab's root carriesFIMSpace.Generating.ObjectStampMultiEmitterand drags 904 dependencies (531GameObjects, elevenRenderTextures) behind it. Measured 19.09.2026 — a synthetic root holding that one prefab came backFailed, 2 errors, 0 files, while theepic-toon-fxprefabs went through. Children are walked too, because a generation component is not required to sit at the root, and a prefab asset is already in memory so the walk costs no loading.Public for the same reason as IsAuthoringData(Type): the content gates ask it too.
- Collect(out List<string>)
Every declared asset, keyed by project path. Problems — a story name that resolves to nothing, an explicit entry whose asset is gone — are reported, never dropped: a declaration that silently loses an entry is the exact bug this design exists to prevent.
- ContentFolderList()
The folders the folder rules scan: every
Prefabs/Animationsfolder inside a shipped pack, plus the declared exceptions. Public because the editor's authoring source needs the same list to answer "is this imported asset inside content?" without walking the project on its own and inventing a second rule.
- DeclaredKeys()
Every declared key, for callers that need the set and not the detail — the editor's authoring source keeps it so a NAME can be resolved to an address without a content-directory build. The complete set, not just the folder rules: name resolution has to see ink areas and the explicit list too.
- InkNames()
Every name the story refers to, from both ways it does it: a placed object's unquoted name, and an
@asset:Type/Namefield reference. A quoted name is a new object the parser creates, not a reference.A placement nested inside a placement that named a registered asset is a child of that prefab, not a reference of its own — the rule that stopped the story-asset test reporting
RoadSign_Post(an object inside theRoad Sign Postprefab) as a missing asset.
- IsAuthoringData(Type)
True when the type, or anything it derives from, is authoring data.
Public so the content gates can ask the same question the declaration answers, instead of keeping a second copy of the rule that would drift.
Two tests, in order. GrindFest.Content.Editor.ContentDeclaration.BuildableDespiteFamily first, because it is the one exception a build proved — a family must not decide for an asset, which is the lesson the namespace test taught by being wrong. Then a name in GrindFest.Content.Editor.ContentDeclaration.NeverContentTypeNames, checked against the whole base-type chain so a project subclass is covered too.
- SplitSubAsset(string, out string, out string, out string)
Splits
Assets/…/X.fbx[ClipName]— orAssets/…/X.fbx[ClipName:AnimationClip]— into the file path and the object inside it. Any other string is a whole asset, so both outputs come back null and the path is unchanged.
- WouldDeclare(string, List<string>, out string)
Whether the folder rules declare this exact asset path, and as what kind.
This is the same test CollectFolders(Dictionary<string, Declared>) applies, extracted so there is one copy of it: the folder scan asks it for every asset an included folder holds, and the editor's authoring source (
Editor/ContentPipeline/ContentAuthoringSource.cs) asks it for the one asset an import callback reported. A second copy is the thing that drifts, and the drift surfaces as content that is in the project, in the build, and silently not in the editor.It answers the folder rules only. The other two collectors are not reachable from a path alone — ink keys are resolved knots and the explicit list lives in a JSON file — so a caller that needs the complete set calls Collect(out List<string>) once and then keeps up through this method.
- WouldDeclare(string, out string)
Convenience overload for a one-off question; builds the folder list first.