assets/ASSET_DROPIN_CONTRACT.md
CANON SUBORDINATION — this document is PROPOSAL-TIER: it serves canon and never outranks it.
The canon: the CVD · the T1 foundation docs · the T0 registries (registries/) · the spine
(docs/spine/CH_*.md) · the region pages (_source/02_Tier_2_Region_Pages/). Authority order:docs/DOC_MAP.md§ 0.
Canon served (scanned from this document's own citations — widen it by hand where it is thin):
T0_Character_Index·T0_Realm_Ruleset_Registry·T0_SFX_Registry·T0_Scene_Spec_Registry· (+4 more t0)
READ THAT CANON FIRST — open it and derive from it before you build anything from this document.
If this document disagrees with canon, CANON WINS and this document is the defect — fix the
document, never the canon. Nothing here is applied until it is ratified into canon.
Status: PROPOSAL-TIER (LANE-CANON). Closes DR-2, "the one absent piece" of the 5090 handshake
(docs/PRE_5090_BUILD_PLAN_VOL2.md §B, row DR-2; audited by the W11 handshake-close wave). Provenance:
the scattered instances already exist (T0.3 ue_sound_asset_path, the cinematic ue_asset_path, the AG7.5
mesh state machine, the T0.13 cross-repo writeback manifest); a grep docs/ for a UNIFIED contract returned
zero. This document is that single contract. Every current-state claim below carries a file:line or a
registry-column citation.
This is ENGINE infrastructure that consumes CANON as data (see Section 5). It invents no world canon; it
declares how a finished asset attaches to a canon id that already exists.
An asset is addressed by its CANON ID, never by a content path.
mesh_id_ref, sfx_id, voice_id, scene_id/scene_anchor_id, a GameplayCue tag, a UMG widget binding. The id is a queryable
UPROPERTY on the staged actor (DR-1 contract) and the RowName of its registry row.
resolver knows the path. The resolver always degrades to a declared placeholder, never to a hard null.
imported to the same path the placeholder occupied (path takeover), or the resolved path is re-pointed in
the id's registry column (manifest re-point). Either way: zero code edits, zero Blueprint edits, zero
re-placement of the staged actor. That is the drop-in guarantee, and it is the whole reason the post-5090
generative passes are pure fill and never rework. Ship-hardening note (critic): a Blueprint
hard-asset reference or a cooked build can bypass a string-path resolver — manifest-re-point
asset directories must be declared always-cook, and the W11 audit gains a BP-hard-ref tooth
(zero Blueprint hard references to generable asset classes) before ship (docs/PRE_5090_BUILD_PLAN_VOL2.md §B exit condition;
docs/ROADMAP_POST_5090_TO_SHIP.md P2.0).
6-D adds the third; rides the commit that adds the columns, and is unrecoverable after the freeze).
presentation_tier_ref is the MASTERY-VARIANT axis: which authored variant of an ability/weapon
presentation plays. generation_tier is the FIDELITY/BUDGET class: which asset-quality ceiling a
surface is generated against (open item 6). decay_stage is the CONDITION axis: which authored
condition of a PLACE renders — not a quality ceiling, not a mastery variant (item 6-D). A bare
tier column or parameter is banned on this contract's schema surface — five live senses already
collide (PresentationTier in code = mastery; the realm charter's tier = fidelity; QA
tier_applicability = footage maturity; the twelve mastery levels = progression;
readability_tier = inscription mastery).
Current violations — the honest migration list. The DataTable/Map id-rail is already clean (34 of the 56
/Game/ string literals in Source/Humanity point at /Game/Data/Registries/DT_* or /Game/Maps/* — that
is the resolver's own input, correctly id-keyed). The ASSET side is not yet under any resolver:
ConstructorHelpers::FObjectFinder binds pin /Engine/BasicShapes/Cube|Cylinder and /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple directly in
actor constructors: HumanityBossCharacter.cpp:92,112,125, HumanityCharacter.cpp:118,130,
HumanityEnemyCharacter.cpp:77,85, HumanitySiteMarker.cpp:38, HumanityGatherNode.cpp:42,
HumanityVrilRechargeZone.cpp:41, plus the Manny skeletal-mesh and anim content paths at
HumanityBossCharacter.cpp:126,128,133. No actor reads mesh_id_ref to choose a mesh (grep of
Source/Humanity/Private for mesh_id_ref as a mesh selector = 0). The mesh class has no id→path resolver
at all.
HumanityAudioSubsystem has a real ResolveSound(FName) with placeholder fallback (HumanityAudioSubsystem.cpp:55-75), but the
cue→path table is six hardcoded /Game/Audio/SFX/SW_* literals (HumanityAudioSubsystem.cpp:32-38) plus
the optional attenuation (:42). It never reads DT_Sfx.ue_sound_asset_path. The column exists; the
resolver ignores it.
So the migration is not "invent a resolver from nothing" — it is "generalize the one audio resolver to read
the id column, and give the mesh classes the same resolver." Roughly 15 mesh-side and 6 audio-side hardcoded (critic-refreshed live count ≈ 21)
asset references move under the contract (Section 6).
Each row: the canon id source, where the placeholder lives today, where the final asset drops, how the swap
happens, and the QA tooth that proves it.
| Asset class | Canon id source (registry.column) | Placeholder home today | Final-asset drop path | Swap mechanism | Verification tooth |
|---|---|---|---|---|---|
| Named-NPC skeletal (MetaHuman) | T0_Character_Index.character_id / mesh_id_ref (+ cinematic ue_asset_path) | SKM_Manny_Simple via ConstructorHelpers (HumanityCharacter.cpp:130) | /Game/Characters/NPC/<char_id> (MetaHuman in-engine) | Manifest re-point (ue_asset_path; MetaHuman lands off-convention) | DataValidation id resolves; capture read by image critic (P3) |
| Boss / creature / familiar / antagonist mesh | mesh_id_ref on Boss/Creature/Familiar/Antagonist assetgen block | /Engine/BasicShapes/Cube (HumanityBossCharacter.cpp:92) | /Game/Meshes/<class>/SM_<mesh_id_ref> (AG7.5 import) | Path takeover (import to convention path) | mesh_id_ref resolves (QT-8); material-identity + capture diff (P3) |
| Weapon mesh | T0_Weapon_Registry.mesh_id_ref (WeaponRegistryRow.h:27) | none built (weapons unrendered) | /Game/Meshes/Weapon/SM_<mesh_id_ref> | Path takeover | DataValidation resolves; capture diff |
| Vril-site / prop / environment mesh | T0_Vril_Site_Registry.mesh_id_ref | Cylinder via ConstructorHelpers (HumanityVrilRechargeZone.cpp:41) | /Game/Meshes/Env/SM_<mesh_id_ref> | Path takeover | imported-mesh visual QA (P3); resolver non-placeholder |
| Material / substrate | substrate_material_ref_path (assetgen block) | M_SiteMarker / biome materials (Content/Terrain/) | /Game/Materials/<class>/M_<id> | Path takeover | material-identity check; capture diff |
| SFX | T0_SFX_Registry.sfx_id → ue_sound_asset_path | /Game/Audio/SFX/SW_*_placeholder + _sonniss (HumanityAudioSubsystem.cpp:32-38) | /Game/Audio/SFX/SFX_<sfx_id> | Path takeover (idempotent import) | cue-fired telemetry non-zero (QT-10); muted/wired fixture (QT-AU) |
| Voice line (VO) | T0_Voice_Registry.voice_id → ue_sound_asset_path | none built (CN-4 slot) | /Game/Audio/Voice/<cat>/V_<voice_id> | Path takeover | cue-fired telemetry; §17.12 gender-lock check (QT-9) |
| Music / theme | T0_Theme_Registry.<theme_id> → ue_sound_asset_path | AU-5 placeholder stems | /Game/Audio/Cues/<cat>/A_<theme_id> | Path takeover | adaptive graph plays real stems; benchmark gate |
| VFX (Niagara) | GameplayCue tag + the closed cue-parameter set (open item 7: element, presentation_tier_ref, status class, counter class, phase) | placeholder cue notifies (C1.16) | Niagara system bound to the same cue tag, branching per-tier on the parameter set — one system per family, never one per variant | Path takeover at the cue binding | every cue fires its effect (QT-6); shape channel independent; per-tier variant identifiable (doctrine §5.4 D10) |
| Cinematic scene | T0_Scene_Spec_Registry.scene_id/scene_anchor_id → scene_spec_ref | scene-anchor runtime placeholder (DT_SceneAnchor) | /Game/Cinematics/Scenes/<scene_id> (Level Sequence) | Manifest re-point (scene_spec_ref) | framed scene plays for FORK_CH02_A; critics read shots |
| HUD art skin | UMG widget binding (authored, not generated) | bound UMG overlay, placeholder art (P2.4) | skinned art in the same widget | Path takeover at the widget | legibility audit (QA-12); before/after captures |
Eleven asset classes — note HUD art WIDENS DR-2's enumerated Vol2 surface list (a disclosed charter
extension, open item 5; ratify or strike at the P0 handshake). Nine ride the five-state generation
machine (Section 3); VFX and HUD art are
hand-authored (Niagara, UMG) and ride only the id + placeholder + swap discipline, with generation_status
marked N/A (open item 5).
The state machine is the ratified AG7.5 enum, stored in the assetgen columns already declared in
docs/registry_extensions.json (generation_status, generation_prompt_hash, last_generated_timestamp,
regeneration_trigger) — the five-state asset enum ratified at AG7.5 (docs/PRE_5090_BUILD_PLAN.md:800) and Vol2 DR-2
(docs/PRE_5090_BUILD_PLAN_VOL2.md:314). (Critic-corrected: the spine/book pipelines run a DIFFERENT
seven/eight-state spine_status machine — no shared enum is claimed.) The human-readable rungs
(placeholder → prompted → generated → reviewed → live) map onto the ratified enum values; the review and
live rungs are process gates and observable outcomes, not extra stored states.
| Human rung | generation_status value | Who writes the transition | Recorded in |
|---|---|---|---|
| placeholder | pending | the staging tool (DR-1..DR-6 placement) | generation_status=pending; placeholder sits at the convention path |
| prompted | in_progress | the 5090 pipeline (prompt composer) | generation_status=in_progress; generation_prompt_hash set |
| generated | complete | the 5090 pipeline (import + apply_*_writeback) | generation_status=complete; ue_asset_path + last_generated_timestamp written |
| reviewed | complete + human sign-off | the QA loop (QA-13) / director / Josh backstop | QA bug-ledger + director_in_loop_required; not a new enum value |
| live | complete + resolver returns non-placeholder | the resolver (runtime, automatic) | observable: the resolver no longer falls to the placeholder |
| (rejected) | rejected | the QA loop / director | generation_status=rejected; the id stays on its placeholder |
| (re-gen) | regeneration_required | a regeneration_trigger (prompt-hash change, canon edit, source correction) | generation_status=regeneration_required; loops back to in_progress |
Two disciplines this encodes. First, "live" is never a stored claim — it is the resolver actually returning
the real asset, so a stale complete on a missing file is caught the moment the resolver degrades to the
placeholder. Second, the cross-repo writeback obeys the one-writer-per-repo rule: the game repo is read-only
against canon, so the pipeline writes results back through the T0.13 manifest and the canon-side applier,
which refreshes docs/fidelity_baseline.json in the same commit (docs/BUILD_PLAN_END_TO_END.md:139).
W11 (HANDSHAKE close) proves the contract holds across every staged actor and cue
(docs/PRE_5090_BUILD_PLAN_VOL2.md MASTER WAVE QUEUE, W11 row). Per staged actor / cue it asserts:
the recorded re-import-renames-a-row / chunk_id-collapse orphan class is exactly what this catches). The
proven precedent is AHumanityVrilRechargeZone::FindForVrilSite keying on VrilSiteId
(HumanityVrilRechargeZone.h:99).
never a crash-null.
generation_status is coherent — a value inside the five-enum; if complete, ue_asset_path is non-empty and loads; if pending, the placeholder loads.
(pending → in_progress → complete → live via the resolver) with zero code or Blueprint diff. That
single round-trip is the handshake exit condition for DR-2.
Per the standing engine-reuse directive (game #2 inherits the machinery, not the world):
conventions, the five-state machine mechanics, the cross-repo writeback manifest + applier, the DR-1
stable-id audit and DataValidation, and the swap-verification teeth (QT-8 data integrity, QT-10 audio
event-fire, QT-AU muted/wired fixture). None of it encodes a single Humanity fact.
which sfx, which voice, which scene; the actual ids, prompts, and prompt hashes; the §17.1 reference-built
vs generatable constraints; the reveal-window gates. Game #2 supplies its own registries and inherits the
resolver, the conventions, and the audit unchanged.
Ordered small batches, each with its own test, each gated by run_gates.py green (unpiped) before it lands.
assetgen owner block plus a ue_asset_path override column to the mesh-bearing registries in docs/registry_extensions.json and docs/fidelity_baseline.json
(added_columns + extension_hashes). Zero code; the mechanism is proven across ten registries already.
Test: harness/check_registry_extensions.py green + the fidelity gate green in the same commit.
✅ DONE 2026-08-02 — Batch 0 is CLOSED, gates 36/36 green, exit 0. Three legs landed together
because they are one commit by ruling: the schema (17 columns across 12 registries, including the
C-12 generation_licence_ref on every enrolled registry, the item-6/6-D reservation columns having
already landed at 28n), the STATE (843 generation_status cells written through the sanctioned
writer harness/apply_generation_lifecycle_backfill.py — 826 empty cells took the enum's own
initial state pending per §3 row 1, since a placeholder at the convention path IS pending), and
the TOOTH (GATE 36 harness/check_generation_lifecycle.py). One correction the migration plan did
not anticipate and that every later Batch-0-shaped pass should inherit: **a schema add that lands a
status column EMPTY re-creates the exact defect Batch 0 exists to close.** T0_Scene_Spec_Registry
therefore received generation_status populated in the same pass, not empty. regeneration_trigger,
last_generated_timestamp and generation_licence_ref do land empty — those are honestly empty
until something generates; a status column is not.
UHumanityAssetResolver (id → path, placeholder fallback),unit-tested headless. Test: a known id resolves to its placeholder; a re-pointed id resolves to the final
path; a missing id returns the declared placeholder, never null.
CueTable (HumanityAudioSubsystem.cpp:32-38) with a DT_Sfx read keyed on sfx_id → ue_sound_asset_path, keeping the placeholder fallback path already in
ResolveSound. Test: QT-10 cue-fired telemetry stays non-zero; the QT-AU muted-build must-not-fire /
wired-build must-fire fixture still passes; the six hardcoded audio literals are gone (grep = 0 outside
the resolver).
mesh_id_ref through the resolver (convention path), with the BasicShape / Manny bind demoted to the
declared placeholder fallback. Test: DataValidation shows mesh_id_ref resolves for every staged actor; a
capture shows the placeholder still renders when no final asset exists; the ten ConstructorHelpers binds now
route through the resolver.
scene_id → scene_spec_ref path; this class is already largely id-driven via DT_SceneAnchor, so this batch is mostly confirming the manifest-re-point path. Test: a framed scene
plays for FORK_CH02_A and image critics read the shots.
before/after captures; the three open HUD polish records (BUG-0006..8) closed or triaged.
Honest sizing: Batches 0-2 are the near-term spine (schema + resolver + the audio proof that round-trips the
state machine — this is the DR-2 handshake exit). Batch 3 is the largest (every mesh-bearing actor). Batches
4-5 are confirmation and polish. None requires the 5090; all are pre-hardware pre-wire.
1. Mesh resolved-path column. The assetgen block carries mesh_id_ref but no resolved-path column; audio has
ue_sound_asset_path and named-NPC has the cinematic ue_asset_path, but boss/creature/familiar/vril/
weapon meshes have only the id. Recommendation: ratify the id→convention path as the default
(/Game/Meshes/<class>/SM_<mesh_id_ref>, resolver-derived, zero column) AND add an optional
ue_asset_path override column for off-convention/licensed/MetaHuman assets. The audio lane already proves
the override-column pattern; one gate-safe column via Batch 0.
2. Reviewed vs live as enum values. The task's five rungs include a distinct "reviewed" and "live"; the
ratified enum has neither. Recommendation: do NOT expand the enum. Record the aesthetic-taste review as a
process gate (QA-13 ledger + director_in_loop_required); treat "live" as the observable resolver outcome.
The five stored states stay pending/in_progress/complete/rejected/regeneration_required.
3. Default swap mechanism. Two mechanisms are valid (path takeover, manifest re-point). Recommendation: make
path takeover the default — the audio import is already idempotent to a fixed path
(Tools/import_audio_assets.py, replace_existing=True) — and reserve manifest re-point for licensed and
MetaHuman assets that must live off-convention. A one-line ratification stops pipelines improvising.
4. The §17.1 reference-built guard. Real living-heritage architecture and forms are NEVER raw-generated
(docs/ROADMAP_POST_5090_TO_SHIP.md P2.2). Recommendation: the resolver's in_progress→complete
transition for a care-gated id is blocked until the capture-rights / authenticity audit signs off — a
mechanical gate keyed on the care-tier column, sitting beside the existing §17.1 collective-care validator
(QT-9). Director confirms the gate wiring.
5. VFX and HUD status semantics. These two classes are hand-authored (Niagara, UMG), not generated, yet they
still ride the id + placeholder + swap invariant. Recommendation: state that the SWAP guarantee applies to
them (placeholder Niagara → real Niagara at the same cue tag; placeholder UMG art → skinned art in the same
widget) while generation_status is marked N/A — the token is now named: not_applicable (Batch 0,
2026-08-02). It is a SENTINEL, not a sixth state: item 2's five stored STATES are untouched, and the live
T0_Voice_Registry already used the same token in two columns before this contract named it. It is not
free to write. GATE 36 (harness/check_generation_lifecycle.py) tooth L5 permits it only where the
registry declares a machine-checkable predicate over live columns (the thirteen cultural_register voice
rows, via voice_clone_status) or a declared lifecycle redirection whose OWNER ROW must exist and be
enum-valid (tooth L6) — and it enforces the converse, so a class cannot be half marked. The column's
vocabulary is therefore closed at five states plus one justified sentinel, with no allowlist and no
per-row exemption — so the W11 audit does not demand a generation state on a
surface that has none.
6. THE REALM-TIER RESERVATION — BINDING, not an open question (the ruled realm-charter amendment; mechanism
verified by the P1-C presentation lens + its fresh-context refutation critic; register v1.4 entry 95).
Recorded here so the freeze carries it. Seven clauses, each riding machinery this contract already
declares:
generation_tier column on the assetgen block with a closed enum that contains realm AT FREEZE: background · region_standard · region_hero · realm · apex. Adding the value later
re-baselines docs/fidelity_baseline.json across every block-bearing registry and re-opens the
frozen contract — so the value exists years before the first realm asset is generated. Lands via
Batch 0 (§6). Honest label: the enum-must-exist-at-freeze argument is sound INFERENCE from the
standing baseline-refresh rule, not a citation — never re-quote it as a cited fact.
generation_tier is a REQUEST parameter and a registry column — NEVER a path segment.Convention paths stay tier-free, so a region→realm fidelity upgrade is a re-generation at the same
id: zero code, Blueprint or actor changes, exactly §1's invariant.
generation_prompt_hash includes the tier — a tier change automatically sets regeneration_required through the existing regeneration_trigger (§3). Self-enforcing: a realm
asset accidentally generated at region tier is caught by the state machine, not by a human noticing.
node_kind. Write generation_tier='realm' onto all TEN realm rows — the nine mythic_realm rows PLUS fairy_realm, which is
node_kind='story_anchor' and silently dropped by the obvious filter, while the safer-looking
page_status='dimensional' over-selects eleven by pulling in the epilogue. NEITHER existing column
is a clean realm selector (register v1.4 entry 105) — which is why the write is explicit. Ten rows,
one script, through the sanctioned writer, fidelity baseline re-emitted in the same commit.
register v1.4 entry 96).
docs/ENGINE_OPTIMIZATION_DOCTRINE.md§5.3/§5.4/§5.6 carry the realm class — realm surfaces inherit the region budgets as their FLOOR
until benchmark day mints realm numbers (doctrine §8.2 U-44). Landed with this clause commit.
docs/proposals/QA_WATCHING_PROGRAM.md §4.4 declares the realmband at freeze, so ceiling surfaces are never graded against a region floor and passed. Landed with
this clause commit.
audio and art references obey the corpus's own ruled wording — NO SAMPLING of consecrated
performance; reference-COMPOSITION is permitted. A blanket reference-first rule is care inflation
and is refused.
corruption-as-condition slice's refutation-survived F-9). Decay is BEAUTY DEGRADED ALONG A NAMED
AXIS: the hue the palette drains toward, the form-transform the growth-forms undergo, the
acoustic thinning — authored ONCE per realm as a RULE applied everywhere, never a mood note, so
a single frame says "this was lovely" and "this is wrong" simultaneously. Five clauses riding
the same machinery as 6.1-6.5:
decay_bearing flag on the assetgen block, landing through Batch 0 (§6) in the SAME commit
as generation_tier. A decay-bearing surface carries decay_stage as a REQUEST parameter
and a registry column — never a path segment. UNLIKE the tier axes, decay stages are
CONCURRENT, not successive: the fairy realm's height is playable at Prologue/Ch 1 while its
fading present is playable at Ch 75-77, so a stage change is NOT a re-generation at the same
bare id (that 6.2 framing governs TIER upgrades only). decay_stage is a VARIANT-KEY axis:
the resolver key is (canon id, decay_stage), and fallback walks to the realm's AUTHORED
PRESENT, never to a hard null — the item-7 shape, reused. The fairy realm's surfaces are
decay-bearing by canon: its fading state IS story canon (the Prologue-height court, the
fading present, the way home).
decay_law field — the named axis, on the art-hook family the realm charter §2.3 DECLARES (palette_law · silhouette_law ·
impossible_architecture_budget · signature_phenomenon), whose registry home is the
Wave-2 T0_Realm_Ruleset_Registry mint (the registry does not exist yet; design intent:
same micro-grammar as palette_law, one parser for the family) — plus the requested
decay_stage. generation_prompt_hash includes the stage (the 6.3 self-enforcement: a
stage-key's prompt change sets regeneration_required for THAT variant through the existing
trigger).
decay_stage vocabulary FREEZES AT EXACTLY TWO VALUES, the endpoints canon alreadypins: the realm AT ITS HEIGHT (the before-state — for the fairy realm, the Prologue-height
palette) and the realm's authored PRESENT. Any intermediate stage a later wave wants is a
DECLARED post-freeze re-baseline priced in its own commit (the 6.1 cost, accepted
deliberately for this axis because two endpoints are canon-sufficient at freeze — this keeps
the DR-2 freeze independent of the Wave-2 mint's schedule).
renewal share one visual vocabulary — so the ruled axis-7 world-cleansing meter moves the
world BACK along the same authored axis (the meter's worldstate key is NOT YET MINTED;
WS_039 is the distinct hollow-containment fork enum, named here so no one freezes the
wrong key). One axis, two directions, never two art sets: the economy argument in contract
form.
endpoint stages); any intermediate stage carries its sizing delta in the commit that mints
it. A realm tier sized without its decay axis buys one palette per realm and then needs two
(the F-9 tooth). Item 6 and this law freeze TOGETHER, exactly as v1.3 entry 92 recorded.
panel generates from the Prologue-height palette (art wave, F-9 rider); the RB-RLM-PROVENANCE
sibling rubric gains the before-state line — shown a fairy-realm frame, the critic states
what the place looked like BEFORE, and if it cannot, decay_law has FAILED on the art side
even where the data passes (zero new gate — rides the existing RB-RLM-CARE rubric family).
is not free: decay-bearing at least DOUBLES the generated-asset count for every surface it
covers. The per-realm decay_law VALUES are art-wave authoring under the realm program's
choice-board protocol.
7. THE PRESENTATION-VARIANT RAIL — FULL PER-TIER, RULED (Josh, 2026-07-28 fourteenth sitting: OPTION (A),
the band-collapse recommendation OVERRULED; register v1.4 entries 94/96/97/100):
presentation_tier_ref lands on the ability/weapon extension rail via Batch 0 (§6) — docs/registry_extensions.json + docs/fidelity_baseline.json in the same commit, the mechanism §6
already calls proven across ten registries.
presentation_tier_ref). Fallback walks DOWN the ladder to theNEAREST AUTHORED tier, never to a hard null — the per-tier extension of §1's placeholder discipline.
The live variant key must be AUTHORED, never synthesized: the foundry currently prints the key from
an id and a clamped tier (its own comment says so), and a derived key is invisible to the audit
tooth, which covers ability_id but not the derived variant key.
8 live tier values), 372 at the 12-tier clamp. Variant volume is a first-class 5090 workload;
parametric interpolation is permitted only INSIDE an authored tier's polish pass, never instead of
one. The every-ability-beautiful requirement stands at full strength.
FGameplayCueParameters surface carries exactly (element, presentation_tier_ref, status class, counter class, phase) —
declared closed HERE so one Niagara system per family branches on parameters instead of forty
systems existing. §2's VFX row carries the same set.
docs/factory_contract.json FR-006's tooth and data_home fields update the moment the rail lands at Batch 0 (today tooth: NONE(...) records the hole verbatim).