Files
MagentaDolphin ecd87ac96d 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>
2026-09-05 23:48:55 +07:00

13 KiB

Patterns: cosmetics and teams in a measured reference product

A worked example of both halves of SKILL.md against one reference project read as source. The two systems are presented together because the audit found what the skill claims: they touch at exactly one point, and both are well built on either side of it.

This file is unusually positive. That is the finding, not a tone: the architecture here is the strongest in the audited project, and the defects are narrow and specific. Separating "this design is worth copying" from "this implementation is finished" is the entire job.

Markers: [measured] — read in source; [derived] — conclusion from measured facts; [open] — not answerable from source, because the call site is in a binary asset.


1. The two-level split, and what it buys

controller component     owns the desired part list, survives respawn
      │  on possession change: remove from old, apply to new, keep handles
      ▼
pawn component           replicated list, spawns presentation, computes tags

The controller component subscribes to possession changes only on the authority, and if a pawn already exists at subscription time it invokes its own handler with a null previous pawn [measured]. That second detail is the pattern's whole robustness: the same code path handles "pawn already here" and "pawn arrives later", so ordering stops mattering.

Transfer removes by handle from the old pawn and re-applies to the new, guarding against double-add by checking whether the handle is already valid [measured].

[derived] This generalises well beyond appearance. Any state that belongs to the player and is realized on the body — loadouts, perks, temporary buffs — has the same shape and the same failure mode if you skip it: the state dies with the pawn.

The link from controller to pawn is found dynamically each time, with no cache [measured]. If the pawn has no cosmetics component the lookup returns null and nothing happens, silently — a tolerance that is correct for optional cosmetics and would be wrong for anything required.


2. Replicating intent, with the split declared in the type

One structure carries both server-only and client-only fields, distinguished by annotation rather than by having two types [measured]:

Field Replicated Lives on
the part itself: class plus socket yes server → clients
the handle returned to the caller no server only
the spawned presentation component no client only

[derived] This is the cleanest available demonstration of "replicate intent, realize locally". The wire carries a class reference and a socket name; the receiving machine reconstructs the appearance. Late join is deterministic because the state is compact and complete.

Change handling is deliberately coarse: the change callback destroys and respawns rather than propagating a diff, and the authors say so in a comment [measured]. Recipe: CT-02.

Part identity is defined as the pair (class, socket) and deliberately excludes the collision mode from the comparison [measured] — so two requests differing only in collision are the same part. [derived] That is a real decision about what identity means, made once, in the comparison function where it belongs.


3. The isolation guarantee, stated precisely

One line excludes presentation from the dedicated server, and it is the only such line in the module [measured].

[derived] The consequence is strong and worth stating exactly: on a dedicated server the cosmetic actors do not exist, so they cannot collide, block a trace, tick, or influence the authoritative simulation in any way. That is a real architectural guarantee obtained for one if.

It is also narrower than the sentence people repeat. On a listen server or in standalone the parts exist on the authority, with whatever their class contains, and nothing in the type system prevents a part class from carrying an ability system or enabled collision. The other protections — collision defaulting to off, authority-only mutators, no ability-system includes anywhere in the module [measured] — are conventions and content discipline. Recipe: CT-01.

The honest form: not present on a dedicated server, not cannot affect gameplay.


4. The ten-line pattern worth stealing

An array of rules, each an asset plus a set of required tags, plus a default. Gather tags, walk the rules in order, first rule whose tags are all present wins, otherwise the default [measured].

It appears at least three times in the audited project — body mesh, forced physics asset, and weapon animation layer [measured] — and costs roughly ten lines each time.

[derived] Its sharp edge is that priority is array order, with no weights and no specificity ranking. A broad rule above a narrow one silently shadows it, and the failure produces a plausible wrong asset rather than an error. Recipe: CT-03.

The tag namespace is declared in config and is deliberately small — a body style and two animation styles [measured]. [derived] Small is the right size: every tag in this namespace multiplies the rule matrix that has to be reasoned about.


5. Where the two systems touch

Exactly one place, and the code says so. The cosmetics component broadcasts a change with a comment noting that observers may need to apply team colouring [measured]; the team display asset applies material parameters to an actor and its child actors by default [measured] — and cosmetic parts are child actors.

[derived] That default is what carries team colour onto modular parts. It is one boolean default, and it is the seam.

[open] The actual call is made from a Blueprint: the applying function has no C++ caller. Which leads to the most important methodological point in this file.


6. What could not be answered, and why that matters

Four things in this subsystem have no C++ call site [measured]:

  • who applies team colours to an actor;
  • who selects the weapon animation layer from cosmetic tags;
  • where the cosmetics component is added to a pawn;
  • where the controller component is added to a controller.

All four are wired in Blueprint or data assets, which are binary and were not read.

[derived] The correct conclusion is "the wiring is in a place this audit cannot see", and the incorrect one — available, tempting, and wrong — is "this function is unused". Both components are marked as spawnable from Blueprint [measured], which is positive evidence for the first reading.

This is the general rule from ue-reference-project-adoption, met in its natural habitat: absence of a C++ caller is not absence of a caller. Anyone deleting "dead" code in a project with a large Blueprint surface needs the editor's reference viewer, not a text search.


7. Teams: one source, mirrors that refuse loudly

Membership lives on the player state, replicated push-based, authority-only, with a non-authority write logged as an error [measured].

Every mirror — controller, bot controller, local player, pawn — reads through to the source, and their setters log an error naming the real owner rather than silently doing nothing [measured].

[derived] This is the single most copyable line in the team system. A silent no-op setter on a mirror is indistinguishable from a working one until something depends on it; an error line converts an invisible failure into a searchable one, at a cost of one statement. Recipe: CT-06.

The pawn mirror is more subtle and also correct: it accepts a direct write only while unpossessed, and refuses with an explanatory error once a controller owns it [measured]. On possession it takes the controller's value and subscribes to its change delegate; on unpossession it unsubscribes and calls an overridable hook whose default is "no team" [measured].

[derived] That hook is an extension point placed exactly where a neutral faction would need it, which is the difference between a design and an implementation that happens to work.


8. Resolution, comparison, and the one permissive branch

Resolution is a four-step cascade — interface, instigator, team-info actor, associated player state [measured]. [derived] The instigator step is what lets projectiles and area effects carry the shooter's team without owning one; a design that omits it grows a team field on every projectile.

Comparison returns three states rather than a boolean [measured], which is correct and is what makes the next finding visible at all.

The damage rule permits four ways [measured], and the third is the finding: when the relationship is indeterminate, damage is allowed if the target has an ability system component — with an author's comment marking it temporary until a training dummy gets a team assignment.

[derived] It exists to make one specific actor damageable and it generalises to every unassigned actor with an ability system. Harmless while the only such actor is a dummy; a rule violation the moment a neutral faction or a destructible objective exists. Solve it in data — give the dummy a team — rather than in the rule. Recipe: CT-08.

Friendly fire is promised by a header comment and implemented nowhere: a project-wide search for any friendly-fire symbol returns zero [measured]. Allies are unconditionally safe. Recipe: CT-07.


9. One policy function, two consumers — the pattern to copy

The same permission function is called by the authoritative damage calculation and by the client-side decision to show a hit marker [measured].

[derived] This removes an entire bug class by construction. "The client showed a hit and the server dealt no damage" requires two implementations of one rule to disagree, and here there is only one implementation. Any project with predicted feedback should copy this arrangement before copying anything else in the team system.

The damage calculation consumes the result as a 0-or-1 multiplier in the formula rather than as an early return [measured]. [derived] The team rule becomes an ordinary participant in the arithmetic, alongside distance and surface attenuation, which keeps the execution pipeline uniform and makes the rule easy to soften later — a scalar between zero and one is already expressible.


10. Presentation as named parameters

The display asset is a bag of named material parameters — scalars, colours, textures, a short name — not a set of assets [measured]. Materials are authored to expect the names, so adding a team costs one asset rather than a duplicated material tree.

Two defects in the applying code, both narrow:

  • Alpha is dropped by the material paths, which convert a four-channel colour to a three-element vector, while the effects path preserves all four [measured]. The same asset therefore behaves differently in two consumers. Recipe: CT-09.
  • A viewer identity parameter is accepted and ignored, with a comment saying so [measured]. The signature and header describe viewer-relative colouring; the implementation returns the absolute asset. Recipe: CT-10.

Reactive binding is done well: an observation action broadcasts immediately on activation so a late subscriber gets current state rather than waiting for the next change, and a second action re-subscribes correctly when the observed team changes [measured]. [derived] Both are small patterns that eliminate a race, and both are reusable anywhere state is observed through a key that can itself change.


11. What to copy, in order

  1. One policy function for authority and prediction (§9). Highest value, lowest cost, removes a whole bug class.
  2. Loud mirrors (§7). One log line per setter.
  3. Controller-owned intent, pawn-owned realization (§1), including the already-possessed case.
  4. Non-replicated fields inside a replicated entry (§2) — one type instead of two.
  5. Rules-plus-default selection (§4), with a shadowing check added.
  6. Immediate first broadcast on subscription (§10).

What to fix while copying: the indeterminate branch (CT-08), the friendly-fire gap (CT-07), the alpha conversion (CT-09), the ignored viewer parameter (CT-10), and the privacy that is a name rather than a mechanism (CT-11).


Provenance

Measured against Epic's Lyra Starter Game on Unreal Engine 5.6: roughly 1300 lines of cosmetics across 11 files and 1800 lines of teams across 22, read as source in a single workspace. Source addresses stay in the research archive that produced this skill; each CT- identifier resolves back to the audited location there.

Evidence boundary

Four wiring call sites are in Blueprint or data assets and were not read; they are marked open above and nothing here depends on them. Everything else describes one project on one engine version. Re-run the recipes against your own tree before trusting any specific claim.