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:
2026-09-05 23:48:55 +07:00
parent 80ea7b03a9
commit ecd87ac96d
67 changed files with 21630 additions and 0 deletions
@@ -0,0 +1,406 @@
# Failure modes: cosmetics and teams
Eleven ways an appearance system or a team system produces the wrong result
without producing an error.
The shared property differs slightly between the two halves, and both are worth
stating.
**Cosmetics fail by looking almost right.** A wrong mesh, a missing part or a
duplicated accessory is a visual outcome, and every visual outcome is plausible
to code. Nothing can assert that a character looks correct, so these defects are
found by people, late, and reported as "the skin is broken".
**Teams fail by permitting.** Every interesting team defect resolves to
"indeterminate", and the branch that handles indeterminate almost always allows
the action, because denying it would have blocked something during development.
Recipes use `rg` from a project source root and were executed against the audited
project while this file was written.
---
## Cosmetics
### CT-01 - The isolation guarantee is one line, and it is narrower than it looks
**Mechanism.** Cosmetic presentation is excluded from the dedicated server by a
single net-mode check at the spawn site. That check is the entire reason cosmetic
content cannot influence authoritative simulation.
**Why it is silent.** It works. On a dedicated server no presentation actor
exists, so none can collide, block a trace or tick. The guarantee holds exactly
where it is claimed.
**Why the obvious check misses it.** The guarantee is usually restated as
"cosmetics cannot affect gameplay", which is a stronger claim than the code
makes. On a listen server or in standalone the parts **do** exist on the
authority, with whatever collision and components their class carries. Nothing in
the type system prevents a part class from containing an ability system or
enabled collision — the constraint is content discipline plus one `if`.
**Symptom.** A cosmetic accessory that blocks a shot, or a part actor that ticks
expensively, in exactly the configuration used for local playtesting — and never
on the dedicated server where the "real" testing happens.
**Detect.** Find the guard, confirm there is only one, and check what part
classes are permitted to contain:
```bash
rg -n "NM_DedicatedServer|IsNetMode" --glob "*.cpp" <cosmetics-module>/
rg -n "CollisionMode|SetActorEnableCollision" --glob "*.cpp" --glob "*.h" \
<cosmetics-module>/
```
In the audited project the first command returns **exactly one line** for the
whole module. That is elegant and it is also the entire safety boundary, which is
worth knowing before relying on it.
**Guardrail.** State the guarantee accurately: "not present on a dedicated
server", not "cannot affect gameplay". Validate part classes at author time —
reject any that contain an ability system component, replicated properties or
enabled collision by default.
---
### CT-02 - Every change rebuilds the actor
**Mechanism.** The replication callback for a changed entry destroys the spawned
presentation and spawns a new one, because propagating a diff into a live actor
is hard and rarely needed.
**Why it is silent.** It is correct. The result after a change is exactly the
result after an add, which is what the system promises. The cost is a rebuild,
and a rebuild looks identical to a first build.
**Why the obvious check misses it.** The implementation is deliberate and
commented as such. Reviewing it shows a decision, not a defect. The consequence
only appears at a usage frequency nobody has yet — a system that changes parts
rarely and one that changes them per second are the same code.
**Symptom.** A hitch on every cosmetic edit. Discovered when a feature starts
mutating parts at runtime — a preview screen, a colour cycler, a progression
system — long after the mechanism was reviewed.
**Detect.** Read what the change callback does, and compare it with the add path:
```bash
rg -n -A12 "PostReplicatedChange" --glob "*.cpp" . | rg -i "destroy|spawn|update"
```
If change is implemented as destroy plus spawn, treat the mechanism as
add/remove only and design callers accordingly.
**Guardrail.** Document the cost where authors will see it. If frequent edits are
planned, add a real update path for the fields that can change in place, and keep
the rebuild for the ones that cannot.
---
### CT-03 - Rule order is priority, and nothing says so
**Mechanism.** A selection set is an array of rules, each with a set of required
tags, plus a default. The first rule whose tags are all present wins. Rules are
not sorted by specificity.
**Why it is silent.** Every possible input produces a result — either a rule or
the default. There is no "no match" state to report, and the result is always a
valid asset.
**Why the obvious check misses it.** The array looks like a set, and sets have no
order. A reviewer checking "are the rules correct?" checks each rule in
isolation, where each is correct. The defect exists only in the relationship
between a broad rule and a narrower one below it, which requires comparing every
pair.
**Symptom.** A character with a specific tag combination gets the generic mesh.
Nobody notices for weeks, because it is the *right kind* of mesh.
**Detect.** Find selection sets and check for shadowing — a rule whose tag set is
a subset of an earlier rule's:
```bash
rg -n -B2 -A6 "SelectBest|RequiredTags" --glob "*.h" --glob "*.cpp" .
```
Then, for each set, verify the shadowing property in a test rather than by
reading: for every pair of rules `i < j`, assert that rule `i`'s tags are not a
subset of rule `j`'s.
**Guardrail.** Write the ordering rule as a comment on the array and enforce the
subset check in validation. A rule that can never win is a content bug the
compiler cannot see.
---
### CT-04 - The realization path re-applies unconditionally
**Mechanism.** Recomputing appearance sets the mesh on every change broadcast,
relying on the setter to be cheap when the value has not changed — while passing
a flag that forces a pose reinitialization.
**Why it is silent.** The result is correct. The mesh is right, the pose is
right, and the cost is invisible at the frequency the system is normally used.
**Why the obvious check misses it.** The code carries a comment explaining that
the setter is a no-op when the mesh is unchanged, which answers the question a
reviewer would ask. The forced flag is a separate argument on the same call, and
it is not what the comment is about.
**Symptom.** A visible pose pop or an animation reset whenever anything about
cosmetics changes, including changes that do not affect the mesh at all.
**Detect.** Check the arguments of the re-application call, not just the call:
```bash
rg -n -B6 "SetSkeletalMesh\(" --glob "*.cpp" . | rg "bReinit|true|Broadcast"
```
A hardcoded reinitialization flag on a path that runs on every change is the
finding.
**Guardrail.** Compare before applying, and pass the reinitialization flag only
when the mesh actually changed. Idempotent realization means "applying twice
costs nothing", not merely "applying twice is correct".
---
### CT-05 - Everything is a hard reference
**Mechanism.** Part classes and selection-set assets are hard references, so the
entire cosmetic catalogue is loaded with whatever holds the rules.
**Why it is silent.** Hard references always resolve. Nothing is ever missing,
nothing ever fails to load, and every asset is available the moment it is needed.
**Why the obvious check misses it.** Reference correctness is what reviews check,
and hard references are maximally correct. The cost is memory residency, which
belongs to a different discipline and a different person, and which does not
appear in any test of the cosmetics system.
**Symptom.** Memory proportional to the size of the cosmetic catalogue rather
than to what is worn, discovered during a platform memory pass and attributed to
content rather than to a reference-type decision.
**Detect.** Count hard versus soft references in the cosmetic data types:
```bash
rg -n "TSubclassOf<|TObjectPtr<" --glob "*.h" <cosmetics-module>/ | wc -l
rg -n "TSoftClassPtr<|TSoftObjectPtr<" --glob "*.h" <cosmetics-module>/ | wc -l
```
A large first number with a zero second is the finding. See
`ue-asset-loading-and-memory` for what this costs and how to measure it.
**Guardrail.** Cosmetic catalogues are the textbook case for soft references and
bundles: large, optional, and mostly unworn. Decide per field and record the
decision in metadata.
---
## Teams
### CT-06 - The mirror setter that silently does nothing
**Mechanism.** Team identity is authoritative on one object and mirrored on
several others. The mirrors implement the interface's setter because they must,
and the implementation does nothing.
**Why it is silent.** Doing nothing is correct — the mirror is not the owner. The
call succeeds, returns, and the value is unchanged.
**Why the obvious check misses it.** The setter exists and is implemented. Any
review asking "does this type support setting a team?" answers yes. The caller
has no return value to check and no reason to suspect the write did not land.
**Symptom.** Code that sets a team on a controller or a pawn and observes the old
value. The investigation goes to replication, then to ordering, then eventually to
the setter.
**Detect.** Read the bodies of every team setter and classify them:
```bash
rg -n -A4 "::SetGenericTeamId|::SetTeamId" --glob "*.cpp" . \
| rg -B1 "UE_LOG|ensure|^\s*\}"
```
The audited project does this **correctly** and is worth copying: the mirror
setters log an error naming the real owner rather than returning quietly. That
one line converts an invisible failure into a searchable one.
**Guardrail.** A mirror's setter logs an error identifying the authoritative
owner. Never implement a no-op setter to satisfy an interface.
---
### CT-07 - The header promises a policy the body does not implement
**Mechanism.** The damage-permission function is documented as taking friendly
fire settings into account. The body contains no settings and no lookup.
**Why it is silent.** The behaviour is coherent — allies are always safe — and
that is the right default for most modes. Nothing malfunctions.
**Why the obvious check misses it.** The comment *is* the documentation. A team
adopting the system reads the header, concludes friendly fire is supported, and
plans a mode around it. Discovering otherwise requires reading a body that looks
finished.
**Symptom.** A mode that needs friendly fire is scoped as a configuration change
and turns out to be a design change, in a function every damage path depends on.
**Detect.** Compare the promise with the implementation:
```bash
rg -n -i "friendly fire" --glob "*.h" --glob "*.cpp" .
rg -n -i "bFriendlyFire|bAllowFriendly|FriendlyFireSetting" --glob "*.h" --glob "*.cpp" .
```
In the audited project the first command finds the promise in a header comment
and the second returns **zero results project-wide**. A promise with no
implementing symbol is the finding, and this recipe generalises to any header
comment describing configurability.
**Guardrail.** Treat a comment describing behaviour as a claim requiring a
symbol. If the setting does not exist, the comment describes a plan and must say
so.
---
### CT-08 - Indeterminate resolves to permitted
**Mechanism.** Team comparison correctly returns three states. The damage rule
treats the indeterminate case as allowed when the target satisfies an unrelated
condition — in the audited project, having an ability system component.
**Why it is silent.** It exists to make something work: an unassigned training
target must be damageable. It succeeds at that, and the permissive branch is
never reached by any assigned actor during normal play.
**Why the obvious check misses it.** The function has three branches and handles
all three, so it passes any review asking whether the indeterminate case is
handled. It *is* handled — permissively — and the marker in the code says
"temporary".
**Symptom.** Any actor with an ability system and no team assignment is damageable
by anyone. Harmless while the only such actor is a target dummy; a rule violation
the moment a neutral faction, a destructible objective or an NPC exists.
**Detect.** Find every consumer of the relationship enum and read its
indeterminate branch:
```bash
rg -n -B4 -A10 "InvalidArgument|Indeterminate|NoTeam" --glob "*.cpp" . \
| rg "return true|= true|Allow"
rg -n -i "//\s*@?TODO.*(temporary|until)" --glob "*.cpp" .
```
The second command is the high-yield one: a permissive branch that its own author
marked temporary is the strongest possible confirmation that the finding is real
and known.
**Guardrail.** Failure to determine must deny. Give the target dummy a team
instead of giving every unassigned actor an exemption — solve the specific
problem in data, not the general rule in code.
---
### CT-09 - A colour conversion that drops alpha
**Mechanism.** A four-channel colour is converted to a three-element vector to be
passed as a material parameter. The fourth channel is discarded.
**Why it is silent.** Three of four channels arrive correctly, so the colour is
right. Alpha is frequently unused in team colours, making the loss invisible for
as long as nobody encodes anything in it.
**Why the obvious check misses it.** The conversion is one token inside an
otherwise correct call. Both types are colours, both are correct, and the review
question — "is the parameter set?" — is answered yes.
**Symptom.** A team parameter that encodes opacity or a blend factor silently
reads as its default. In the same file, the effects-system path preserves all four
channels, so the same asset behaves differently in two places.
**Detect.** Find lossy conversions at parameter-setting sites:
```bash
rg -n "SetVectorParameterValue\w*\(.*FVector\(" --glob "*.cpp" .
rg -n "SetVariableLinearColor|SetVectorParameterValue" --glob "*.cpp" .
```
Two applicators for the same data with different channel counts is the finding.
In the audited project the material paths convert and the effects path does not.
**Guardrail.** Pass four channels where the API accepts them. If a conversion is
unavoidable, assert or document that alpha is unused.
---
### CT-10 - An accepted parameter that is ignored
**Mechanism.** The display-asset accessor takes a viewer identity so that
presentation can be relative — "my team always looks blue". The implementation
does not read it.
**Why it is silent.** The function returns the correct absolute asset. Every
caller gets a valid result, and the feature the parameter implies has simply
never been exercised.
**Why the obvious check misses it.** The signature and the header comment
describe the feature completely. Confirming absence requires reading the body,
where the parameter is unused — and an unused parameter produces no warning
because it is part of a virtual-looking public API.
**Symptom.** A team implementing viewer-relative colours writes correct calling
code and observes absolute colours. The bug appears to be in their code.
**Detect.** For any parameter that names a feature, check that the body reads it:
```bash
P='ViewerTeamId'
rg -n "\b$P\b" --glob "*.h" --glob "*.cpp" .
```
Occurrences only in the declaration and the definition's signature — with none in
the body — is the finding. In the audited project the body carries a comment
stating the parameter is currently ignored, which is honest and still ships an
API that cannot do what it claims.
**Guardrail.** Do not accept a parameter you do not use. Remove it, or implement
it, or make the function name state the limitation. This is the same defect class
as an ordering field that never sorts — see `ue-ui-architecture`, UI-01.
---
### CT-11 - "Private" that is not filtered
**Mechanism.** Team data is split into a public and a private actor to express a
replication boundary. The private one has no filtering; it is an empty subclass
with a note that privacy is not implemented.
**Why it is silent.** Everything works. Both actors replicate, both carry their
data, and the split is structurally correct — it is only the *filtering* that is
absent.
**Why the obvious check misses it.** The architecture is visible and right: two
types, two names, a clear intent. Reading the class names answers the question.
Confirming means noticing that the private subclass has no body.
**Symptom.** Data placed in the private actor because it is private is replicated
to every client. Nothing indicates this until someone inspects network traffic —
or until a competitor does.
**Detect.** Check that the privacy-named type actually filters:
```bash
rg -n -A15 "class \w*PrivateInfo|class \w*Private\w*" --glob "*.h" .
rg -n "IsNetRelevantFor|GetLifetimeReplicatedProps|COND_" --glob "*.cpp" . \
| rg -i "private"
```
An empty subclass, or one with no relevancy or condition logic, is the finding.
**Guardrail.** A name is not a mechanism. Either implement the filtering — via
relevancy, replication conditions or a replication graph — or rename the type to
what it is. In the meantime, do not put anything in it that matters.
@@ -0,0 +1,288 @@
# 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
```text
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.