feat(skills): ship ue-design-skills bundle, licensing and delivery gate

Phase 0 of the handoff plan, as a marketplace rather than a flat skills/
directory. Content moved out of the LyraResearch archive and depersonalised:
addresses stay in the archive, recipes ship.

- plugins/ue-design-skills: 17 skills, 232 failure-mode entries, each with the
  six required fields; catalog.json as the harness-neutral source of truth and
  .claude-plugin/ as one adapter over it.
- _gate: 16 rules, one poisoned fixture per rule, plus surface coverage so a
  declared file cannot silently miss the line rules.
- ADR-0002 (harness-neutral bundle behind a marketplace) and ADR-0003 (split
  licensing: CC BY-ND 4.0 prose, Apache-2.0 code and metadata).
- LICENSE files at both levels, CONTRIBUTING.md, docs/licensing-options.md as
  the material the licence decision grew from.

Verified: gate.py 0 violations; test_gate.py 16/16 rules redden on their
fixtures with a clean baseline and 2 root files reaching the line rules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ue-toolchain
2026-09-05 23:48:55 +07:00
parent 9e2194c298
commit dab3f35079
67 changed files with 21630 additions and 0 deletions
@@ -0,0 +1,413 @@
---
name: ue-data-driven-architecture
description: >-
Design or review data-driven architecture in Unreal Engine: definition layers,
data asset versus zero-node Blueprint class defaults versus instanced
fragments and actions, primary asset identifiers, soft and hard and class
references, asset bundles, modular composition, validation, and the boundary
between code and data. Use when adding game modes, pawn archetypes, ability
packages, item and equipment definitions, playlists or feature plugins, or
when a manager is growing per-variant branching.
---
# UE data-driven architecture
Data-driven does **not** mean "put fields in a data asset". It means:
> Code owns lifecycle, algorithms, replication and invariants. Data selects and
> composes implementations at explicit architectural layers.
Measured patterns from a reference product: [patterns](references/patterns.md).
Detection recipes for silent data-graph failures:
[failure modes](references/failure-modes.md).
**The characteristic failure of a data graph is that it compiles.** A wrong
reference, an unregistered identifier, a missing bundle and a copy-pasted
prototype are all valid data. Validation is the only compiler this layer will
ever have, and building it is part of the architecture rather than a follow-up
task.
Related skills: `ue-modular-gameplay`, `ue-gas-architecture`,
`ue-input-architecture`, `ue-ui-architecture`, `ue-cosmetics-and-teams`.
Method, not architecture — how to check any claim in this bundle before repeating it: `ue-evidence-discipline`.
---
## 1. Start with layers, not asset classes
Before creating an asset, name the level of composition it owns. A robust
product usually needs several narrow definitions, not one omniscient
configuration:
```text
catalog / playlist
├─ where and how to launch (map, session, display metadata)
└─ gameplay definition
├─ feature plugins to enable
├─ reusable action sets
├─ mode-specific delta
└─ pawn archetype
├─ pawn class
├─ capability packages
├─ input semantics
├─ policy matrix
└─ camera
```
Items form a separate pipeline:
```text
pickup presentation
└─ item definition
├─ inline fragments (display, stats, equippable, reticle)
└─ equipment definition
├─ runtime instance class
├─ capability packages
└─ actors and components to spawn
```
### The layer test
For every field ask:
1. Does it describe **launch and catalog UI**, or **runtime gameplay**?
2. Is it common to several modes, or a mode-specific delta?
3. Does it define an archetype, a capability, or presentation?
4. Who consumes it, and at which lifecycle phase?
If one asset answers all four, split it.
---
## 2. Pick the container by lifecycle
The storage type is an architectural decision, not a preference.
| Need | Use | Why |
|---|---|---|
| Standalone immutable record referenced as an object | data asset | object identity, editable in Details |
| Asset-manager discovery, bundles, async loading | primary data asset | stable identifier, bundle rules |
| A class default, a class reference, inheritance, or a runtime instance of that class | zero-node Blueprint subclass | class identity plus editable defaults |
| Polymorphic optional facet embedded in a definition | instanced object, edit-inline | composition without asset explosion |
| Polymorphic load/activate/deactivate operation | instanced action object | behaviour in code, targets in data |
| Dense homogeneous tabular records | data table | rows, import and export, bulk editing |
### A Blueprint with zero nodes is a data container
This is the most consequential and least visible fact in this skill. An entire
definition layer can live in Blueprint classes that contain no graph at all,
with values on their class defaults.
**An inventory that searches only for data-asset instances will not see any of
it.** Count both, or your census of the data layer is wrong by however much that
layer holds. Recipe: DD-01.
### When not to use a Blueprint class default
Do not use one merely because the editor offers it. Prefer a data asset when
consumers need an object rather than a class, when no runtime instance of the
subclass is created, when inherited defaults add nothing, and when asset-manager
identity matters more than a class reference.
---
## 3. Pick reference semantics explicitly
Every reference chooses loading and ownership behaviour.
| Reference | Use when | Failure mode |
|---|---|---|
| primary asset identifier | the target must be addressed before it is loaded | type or name not registered; resolves to nothing |
| soft object or class pointer | async load, optional content, role-specific content | not loaded when dereferenced; omitted from the build |
| hard pointer | the target is required whenever the owner is loaded | dependency graph grows transitively |
| class reference | data selects an implementation class or its defaults | construction and lifecycle become class-coupled |
| instanced object | the owner exclusively owns a small polymorphic facet | duplication; cannot be shared independently |
Write the decision into the property metadata and the validation, not only into
a design document.
### Asset bundles belong beside the soft reference
```cpp
UPROPERTY(EditAnywhere, meta=(AssetBundles="Client"))
TSoftClassPtr<UUserWidget> WidgetClass;
UPROPERTY(EditAnywhere, meta=(AssetBundles="Client,Server"))
TSoftClassPtr<UGameplayAbility> AbilityType;
```
The definition then declares both **composition** and the **load and cook
boundary** in one place. Do not maintain a separate, undocumented table of which
assets belong to which role.
Two failure modes follow directly, and both are silent:
- a soft reference with **no** bundle annotation is not pulled into the build by
its owner, so it resolves to null in a packaged game and works in the editor;
- a bare accessor on a soft reference assumes the bundle worked, and returns null
without complaint when it did not.
Recipes: DD-03, DD-04.
### Cross-plugin dependency gate
For every reference crossing a feature-plugin boundary: is the target plugin
active before this asset resolves? Does the owning bundle pull it into the
build? Can the source deactivate while the target is still referenced? Is the
dependency one-way, or did you just create a cycle?
---
## 4. Code consumer, data selection
A healthy definition has a narrow code consumer. The consumer owns when loading
happens, authority and client rules, replication, initialization order, apply and
rollback, validation and error handling.
Data owns which class or asset or tag is selected, numeric and presentation
parameters, variant composition, and role flags where the consumer supports them.
### The switch test
If adding a new mode, weapon or pawn requires another branch in a manager, a
definition, action or fragment boundary is probably missing.
### But do not move an invariant into data
These stay in code, always:
- "only the authority grants capabilities";
- "the archetype must exist before this readiness state";
- replication rules;
- teardown order;
- "one grant revokes only its own handles".
**The variant is data; the safety rule is code.** A rule that can be edited by
opening an asset is a rule that will be edited by someone who does not know it is
a rule.
---
## 5. Compose reuse; do not hide it in inheritance
Prefer flat composition:
```text
experience = shared input + standard components + standard HUD + mode delta
```
over an inheritance chain:
```text
base → shooter → team → deathmatch
```
Composition makes dependencies visible, reusable and diffable. Inheritance
accumulates defaults silently and creates ordering questions nobody asked.
If your definition type supports Blueprint subclassing, consider **rejecting it
in validation** and pointing the author at composition instead. A reference
product does exactly this, with an error message naming the alternative
— a validator that teaches rather than only refuses. Recipe: DD-05.
### The symmetry gate for actions
Before approving any action that adds something at runtime, fill this table:
| Resource added | Handle retained | Exact rollback |
|---|---|---|
| extension handler | request handle | release handle |
| component | component request or instance | remove only the owned component |
| capability grant | granted handles | revoke only those handles |
| input binding | bind handles | remove each binding |
| mapping context | player plus context | remove from the same player |
| widget or layout | extension or layout handle | unregister or remove |
**A blank middle column means the action cannot be rolled back.** Do not
advertise runtime-deactivatable modularity until the table is complete and
tested. See `ue-modular-gameplay` for the failure modes this prevents.
---
## 6. Use fragments for optional facets
Avoid one definition carrying every possible field for every possible item.
Use polymorphic inline fragments and let consumers query by class:
```cpp
UCLASS(DefaultToInstanced, EditInlineNew, Abstract)
class UItemFragment : public UObject {};
UPROPERTY(EditDefaultsOnly, Instanced)
TArray<TObjectPtr<UItemFragment>> Fragments;
```
The definition stays open to new facets without changing its base class,
irrelevant fields do not appear on unrelated items, and designers compose facets
in the editor.
Fragment rules: one responsibility each; state whether duplicates are legal;
validate required combinations; never depend on array order unless the order is
named and documented; fragments select and configure behaviour, they do not own
lifecycle.
---
## 7. Put cross-cutting policy in a policy definition
When many capabilities repeat the same relationships, do not copy the same
containers into all of them. Use a matrix selected by the archetype, so the same
capabilities can run under different policies.
Good candidates: capability relationships, presentation parameter bags,
sensitivity curves, class-to-widget mappings, quality thresholds.
Bad candidates: authority decisions, anti-cheat rules, irreversible lifecycle
transitions, and the validation logic itself.
---
## 8. Validation is part of the architecture
A data graph creates failure classes that code-only systems do not have. Every
definition family needs validation, and it needs to run somewhere other than the
editor.
### Universal checks
- required references non-null;
- arrays with no effective entries rejected;
- class derives from the required base or implements the required interface;
- tags valid and constrained to the expected category;
- identifiers resolve;
- soft targets belong to available, cooked plugins;
- duplicate semantic keys detected;
- client-only assets not bundled for the server, and the reverse;
- every action has a complete rollback path;
- production definitions reference no prototype or test content.
### Cross-family checks
These are the ones that catch real defects, because they cross an ownership
boundary that no single reviewer owns:
```text
playlist identifier → resolves to a registered gameplay definition
gameplay definition → archetype has a class, or is explicitly empty
archetype capabilities → input tags match a reachable input config
item equippable fragment → resolves to an equipment definition
equipment definition → capability packages valid for its instance type
pickup presentation → matches the item definition it claims to present
```
The last row is not theoretical. In the audited reference, two health pickups
reference the pistol item definition. It compiles, cooks, and ships. Recipe:
DD-07.
### Editor-only validation is feedback, not a boundary
If invalid data can reach a packaged build through an automated cook, the
validation must run there too. In the audited reference **eleven of twelve
validation entry points are compiled out of non-editor builds** — which is
normal, correct, and exactly why a commandlet or automation test has to exist
beside them. Recipe: DD-09.
---
## 9. Auditing an existing project
### A. Inventory both containers
1. Query the asset registry for instances deriving from the data-asset base.
2. Query the class registry for zero-node classes whose parent is a definition
type.
3. Include every mount root — the primary content root alone is incomplete in a
plugin-based project.
4. Exclude world-partition external actor packages from asset counts.
**Do not read binary asset files from disk.** Use the asset registry and live
editor APIs. This is a hard constraint, not a preference: a text search over
binary assets produces confident nonsense.
### B. Build the reference graph
For each definition record the owner layer, the consumer, the identifier, the
reference kinds, the bundle metadata, and the incoming and outgoing plugin
boundaries.
### C. Measure behaviour density
A definition class should have zero or very few graph nodes. A definition
carrying a large graph is secretly both data and manager.
### D. Trace one complete variant end to end
Do not stop at class declarations. Pick one real variant and follow it:
```text
playlist → definition → action sets → archetype
→ capability and input policy → item → equipment → grant
```
Record the actual configured values. Generic reflection frequently hides
inherited class-default fields and reports zero properties on a populated
object; read known fields by name from the schema instead of concluding the
object is empty. Recipe: DD-02.
---
## 10. Review checklist
- [ ] The definition's architectural layer has a name.
- [ ] It has exactly one consumer owner.
- [ ] Container type matches lifecycle.
- [ ] Reference kinds are intentional and written in metadata.
- [ ] Bundle metadata matches client and server use.
- [ ] Reuse is composition, not a chain of inherited defaults.
- [ ] Actions and fragments have focused responsibilities.
- [ ] Apply and rollback symmetry is proven, not assumed.
- [ ] Required links and semantic joins are validated.
- [ ] Cross-plugin references are acyclic and cooked.
- [ ] A real instance was traced end to end.
- [ ] Production data contains no prototype or copy-pasted references.
If more than two boxes are unknown, the architecture is not data-driven yet — it
is data-shaped.
---
## 11. When to stop
Introduce a definition layer when at least one is true: several modes reuse the
same composition; content ships after launch; variants are authored by people who
do not write code; a manager is accumulating per-variant branches.
Do not introduce one because the reference project has it. Every layer costs an
asset type, a validation surface, a loading path and a debugging hop. The
measured benefit of this architecture is that a mode becomes a list rather than a
subclass — if you have one mode, you are paying for an answer to a question you
do not have.
---
## Provenance
The measured material comes from an inspection of Epic's Lyra Starter Game on
Unreal Engine 5.6, performed through a live editor bridge rather than by reading
files: 84 native data assets across 21 runtime classes, 23 zero-node definition
classes, and 37 inline actions across ten definitions and five action sets. That
method matters — see DD-01, which exists because a purely text-based census of
this layer is structurally incapable of seeing most of it.
Source addresses and asset paths stay in the research archive that produced this
skill. Each entry carries a stable identifier (`DD-01`, `DD-03`, …) resolving
back to the audited location there.
## Evidence boundary
Values obtained through the editor describe one project at one moment. Graph
assets whose internals no safe reader exposed — environment queries, state trees,
blackboards — were listed but not read, and are marked as such rather than
summarised. Re-run the recipes against your own project; the counts here show the
shape of a contrast, not a target.