Table of Contents

Class ContentDeclaration

Namespace
GrindFest.Content.Editor
Assembly
GrindFest.dll

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 Prefabs anywhere" was the first version, and it declared 18656 assets — TopDownEngine/ThirdParty/MoreMountains demo content, a Sci-Fi and a Kids art pack nothing in the game references, and every part of PolygonFantasyHeroCharacters. The pack list is the set of packs the shipped catalogue actually used, so it is evidence, not preference.
  • Never under Models. A pack's Models/ folder is source art — 6647 .fbx files in this project — and GetMainAssetTypeAtPath reports an imported model as a GameObject, 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 in Prefabs/Items, rooms in Dungeons/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
object
ContentDeclaration

Fields

ExplicitFileName

Where the explicit list lives. Hand-editable, which is the point.

Properties

ExplicitPath

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 carries FIMSpace.Generating.ObjectStampMultiEmitter and drags 904 dependencies (531 GameObjects, eleven RenderTextures) behind it. Measured 19.09.2026 — a synthetic root holding that one prefab came back Failed, 2 errors, 0 files, while the epic-toon-fx prefabs 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/Animations folder 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/Name field 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 the Road Sign Post prefab) 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.

ResolveByShortName(string)
SplitSubAsset(string, out string, out string, out string)

Splits Assets/…/X.fbx[ClipName] — or Assets/…/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.