Skip to content

t1k:unity:tof:ui-create-prefab

FieldValue
Moduletof
Version2.2.2
Effortlow
Tools

Keywords: anchor pivot, Background, BasePuzzlePopup, BasePuzzleScreen, Content, content children, field-driven, FlexibleLayoutGroupVer2, Foreground, HyperCasualRootUI, layout image, popup prefab, prefab variant, puzzleui, responsive ui, SafeArea, screen prefab, theonefeature, theonefeature ui, Timeline_Grp, tof, TOFUIs, ui prefab, unity mcp

/t1k:unity:tof:ui-create-prefab
[screen-name] [layout-image-path] (both optional)

Create TheOneFeature UI Prefab (Screen / Popup)

Section titled “Create TheOneFeature UI Prefab (Screen / Popup)”

Prefab-only authoring guide for TheOneFeature-scoped UI screens / popups. Scope is the .prefab asset: variant of base templates, hierarchy (Background / Content / Foreground / Timeline_Grp), SafeArea scope, anchor/pivot, layout groups, field-driven Content children, Addressables registration. Script side (View / Presenter / [ScreenInfo]) is OUT OF SCOPE — covered by other tof skills.

Mirror counterpart: t1k:unity:ui:create-prefab (at .claude/skills/t1k-unity-ui-create-prefab/SKILL.md) — generic Unity UI prefabs outside TOF paths. When editing either skill, mirror the change.

Scope — When to Use This Skill vs. the Generic Skill

Section titled “Scope — When to Use This Skill vs. the Generic Skill”
Target prefab pathUse skill
Packages/TheOneFeature* (anything under any TheOneFeature.* package)this skill
Assets/Prefabs/UIs/ (project-level TOF screen registry path)this skill
Anything else — Packages/com.gdk.<x>/, Packages/<consumer>/, Assets/Prefabs/<Feature>/UI/, project-side feature packages that do not use the TOF runtimet1k:unity:ui:create-prefab

If the target path does NOT match a TOF row above, STOP and switch to the generic skill — generic screens variant from a different base (UIBaseScreen.prefab / UIBasePopup.prefab in com.gdk.core), register in a different Addressables group, and parent to RootUI.prefab instead of HyperCasualRootUI.prefab.

Authoring Tool — Unity MCP (with documented limits)

Section titled “Authoring Tool — Unity MCP (with documented limits)”

Default to the Unity MCP bridge (mcp__UnityMCP__manage_prefabs, mcp__UnityMCP__manage_gameobject, mcp__UnityMCP__manage_components, mcp__UnityMCP__manage_asset). See §10 MCP Tool Capability Matrix below for which actions actually work today — two operations require direct YAML edits as a fallback: (a) creating a prefab variant (MCP create_from_gameobject has no variant flag), and (b) adding an asset to an Addressables group (manage_asset has no addressables_add action). When you do fall back to YAML, follow the exact patterns in §1 (variant) and §7 (Addressables) — they pattern off existing working files in the project (HomeScreenView Variant.prefab, UIs.asset), not free-form.

See t1k:unity:base:mcp-skill for the MCP install/connect procedure and rules/unitymcp-the1studio-fork-only.md for the canonical install.

IntentPath
”New full screen”Create variant of BasePuzzleScreen.prefab§1 Create as Variant
”New popup / modal”Create variant of BasePuzzlePopup.prefab§1 Create as Variant
”Spawn children for each [SerializeField] on the View”§9 Materialize Content Children
”User provided a layout image”§9.4 Layout image input
”Add full-bleed background image”Place under Background§2 Layer scope
”Add main UI / popup body”Place under Content§2 Layer scope
”Add HUD overlay / toast”Place under Foreground§2 Layer scope
”Hook open/close animation”Bind to Timeline_Grp§3 Timeline_Grp
”Set anchors / pivot on a child”§5 Anchor / pivot rules
”Grid that reflows by aspect ratio”§6 Layout groups
”Register prefab in Addressables”§7 Addressables registration
”Which MCP actions actually work?”§10 MCP tool capability matrix

Pick exactly one base — never start from an empty Canvas, never duplicate the base (variant so future base fixes propagate).

NeedBase prefabGUID source
Screen (full-page view)Packages/TheOneFeature.PuzzleUI/Common/Prefabs/Base/BasePuzzleScreen.prefabread <base>.meta for guid:
Popup / modalPackages/TheOneFeature.PuzzleUI/Common/Prefabs/Base/BasePuzzlePopup.prefabread <base>.meta for guid:
  1. Instantiate the base in a temp scene: mcp__UnityMCP__manage_gameobject(action="create", name="<ScreenName>", prefab_path="<base-path>")
  2. Attach the View MonoBehaviour: mcp__UnityMCP__manage_gameobject(action="modify", target="<ScreenName>", search_method="by_name", components_to_add=["<FullyQualified.ViewType>"])
  3. Check capability for variant save: does mcp__UnityMCP__manage_prefabs.create_from_gameobject accept a variant parameter? As of 2026-05-28: NO — the action’s schema has only unlink_if_instance, which produces a STANDALONE prefab, not a variant. Until upstream The1Studio/unity-mcp adds the flag, skip step 4 and go to Track B.
  4. (Future, when MCP adds variant support) mcp__UnityMCP__manage_prefabs(action="create_from_gameobject", target="<ScreenName>", prefab_path="<target>", variant=true).

Track B — Minimal-YAML variant (current reality)

Section titled “Track B — Minimal-YAML variant (current reality)”

Track B is the mandatory fallback today. It writes a variant .prefab file directly that patterns the existing Assets/Prefabs/UIs/HomeScreenView Variant.prefab (a working 37-line minimal variant). It is NOT free-form YAML — it is a fixed template with three substitutions.

Inputs to gather before writing:

VariableHow to derive
<base-guid>head -3 <base>.prefab.metaguid: line
<base-root-go-fileID>See §1.5 lookup algorithm
<screen-name>The View class name (e.g. GameplayScreenView)
<view-script-guid>head -3 <ViewName>.cs.metaguid: line
<random-int64-A>, <random-int64-B>Two distinct positive int64 anchor IDs (e.g. 5844738139906543051, 5844738139906543052)

Template (write to <target-path>.prefab):

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &<random-int64-A>
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: <base-root-go-fileID>, guid: <base-guid>, type: 3}
propertyPath: m_Name
value: <screen-name>
objectReference: {fileID: 0}
# 20 RectTransform overrides identical to HomeScreenView Variant.prefab
# (pivot.x/y, anchorMin.x/y, anchorMax.x/y, sizeDelta.x/y,
# localPosition.x/y/z, localRotation.w/x/y/z, anchoredPosition.x/y,
# localEulerAnglesHint.x/y/z) — all set to defaults (pivot 0.5,0.5;
# anchorMin 0,0; anchorMax 1,1; everything else 0 or identity)
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: <base-guid>, type: 3}

After writing the file, attach the View script as a SEPARATE step (Track B + then re-instantiate + add component via Track A.2 + use mcp__UnityMCP__manage_prefabs.modify_contents to write back to the variant). Reason: embedding the m_AddedComponents block by hand requires synthetic fileIDs and a property block that is fragile; let MCP own it once the variant file exists.

Wiring follows in §9.3 Track A (direct YAML edit) — MCP cannot wire in-prefab object references today. Plan for that step when budgeting variant authoring time.

Refresh: call mcp__UnityMCP__refresh_unity(scope="assets", mode="force") so Unity imports the new asset and assigns it a GUID for §7 Addressables registration.

Validation: after refresh, the new .prefab must:

  • start with --- !u!1001 &<id> (PrefabInstance, NOT GameObject)
  • end with m_SourcePrefab: ... guid: <base-guid> matching the chosen base
  • import without console errors

If the file fails import, copy HomeScreenView Variant.prefab verbatim, swap only the m_Name value, and re-test — that proves the GUID values are correct.

The root-GO fileID is the GameObject fileID that the variant must reference for m_Name and root-RectTransform overrides. Two cases:

Case A — base is a standalone prefab (e.g. a non-variant base):

  • The root GO fileID is the &<id> anchor on the first !u!1 GameObject block in the prefab file.
  • Quick lookup: grep -n -m1 '!u!1 &' <base>.prefab → take the number after &.

Case B — base is itself a variant (e.g. BasePuzzleScreen is a variant of BaseUIScreen):

  • The root GO is NOT declared in the base file — it lives in the grandparent. But the base file references it through its own m_Modifications block.

  • Lookup: find the GO fileID that the base uses as a target: for its root-level overrides (the m_Name override at the very end of the base’s modifications list is the canonical pointer).

    Terminal window
    grep -B1 -A3 'propertyPath: m_Name' <base>.prefab | tail -5
    # Reads as: target: {fileID: <ROOT-GO-FILEID>, guid: <grandparent-guid>, type: 3}
  • The <ROOT-GO-FILEID> and grandparent guid are what you write into the variant — NOT the base’s own GUID. Inheritance chains in Unity variants always reference the originating GameObject’s fileID, even though m_SourcePrefab points at the immediate parent.

If a base prefab does not contain an m_Name propertyPath modification (no root rename), find any target: line in its m_Modifications block — every override targets the same root GO fileID for root-level properties.

2. Layer Scope — Background / Content / Foreground

Section titled “2. Layer Scope — Background / Content / Foreground”

The base prefab ships three sibling layer containers under the root. Place new GameObjects under the layer matching their visual role. SafeArea only wraps Content — the other two are full-bleed.

LayerSafeArea?Render orderWhat goes here
BackgroundNOBottomFull-screen images, gradients, video backdrops, particle backgrounds.
ContentYESMiddleMain UI: popup body, panels, buttons, lists, HUD elements that must stay inside the safe area.
ForegroundNOTopOverlays that float over Content AND extend to screen edges: toasts, FX, tutorial pointers.

Rules:

  • Never place full-bleed art under Content — SafeArea will inset it and reveal letterboxing on notched devices.
  • Never place tap-targets / readable UI under Background or Foreground — they can fall under the notch or the gesture bar and become unhittable.
  • Do NOT add a second SafeArea anywhere. The one on Content is the only SafeArea in the prefab.
  • Do NOT add a CanvasScaler or AspectRatioFitter to the variant root — CanvasScaler lives ONLY on HyperCasualRootUI.

3. Timeline_Grp — Open / Close Animations

Section titled “3. Timeline_Grp — Open / Close Animations”

The base ships Timeline_Grp with two PlayableDirectors (Tl_Appears, Tl_Disappears). Customize per-screen by overriding the bound Timeline assets on the variant — never edit the base’s Timeline assets directly. If no custom anim needed, leave it untouched. Never delete Timeline_Grp — the open/close transition silently no-ops without it.

Author per-screen Timeline assets under Packages/TheOneFeature.PuzzleUI/<Feature>/Timelines/<ScreenName>_Appear.playable and <ScreenName>_Disappear.playable.

If the SAME widget appears twice or more anywhere in the project, it MUST be a prefab and instantiated as a prefab reference — never duplicated GameObjects.

Existing reusable widgets (compose with these first):

NeedPrefab
Currency / coin counterCurrencyView.prefab
Title barTitle.prefab
Bottom tab barFeature/bottom_nav_bar_location.prefab
Reusable buttonFeature/btn_feature.prefab
Coin visualsCoins/prefab_coin*.prefab
Timer / clockTimer.prefab, Clock.prefab
Animated numberTextCounting.prefab, NumberHolder.prefab
Lives indicatorLivesView.prefab
Error / retry stateErrorRetryView.prefab
Loader spinnerLoader.prefab
Notification badgeic_noti.prefab
Background gradientBgGradient.prefab, BgTop.prefab

If a needed widget is not in the catalog AND you’ll use it 2+ times, create a new prefab in Common/Prefabs/<Category>/ BEFORE adding the second instance.

Core principle: the pivot lives on the same edge as the anchor. Stretch ONLY the axis that must flex with device size; fixed-axis elements use coincident min=max anchors.

Element roleAnchor min → maxPivot
Layer container (Background / Content / Foreground)(0,0) → (1,1)(0.5, 0.5)
Header / title bar(0,1) → (1,1)(0.5, 1)
Footer / bottom nav(0,0) → (1,0)(0.5, 0)
Top-right HUD (coins, settings)(1,1) → (1,1)(1, 1)
Top-left HUD (back, menu)(0,1) → (0,1)(0, 1)
Bottom-right corner button(1,0) → (1,0)(1, 0)
Bottom-left corner button(0,0) → (0,0)(0, 0)
Centered popup body(0.5,0.5) → (0.5,0.5)(0.5, 0.5)
Horizontal stretched bar at y=Y(0,Y) → (1,Y)(0.5, Y)
Vertical stretched bar at x=X(X,0) → (X,1)(X, 0.5)
PatternComponent
Simple 1-axis horizontal listUnity HorizontalLayoutGroup
Simple 1-axis vertical listUnity VerticalLayoutGroup
Grid that must reflow rows×cols by aspect ratioFlexibleLayoutGroupVer2 (Packages/TheOneFeature/UI/Utilities/UILayout/) preset BaseOnContent

Never use Unity GridLayoutGroup for responsive grids — it fixes cell count, breaking on different aspect ratios.

Required so ScreenManager.OpenScreen<T>() can resolve the prefab via the [ScreenInfo] key.

7.1 Detect the correct group asset (project-specific)

Section titled “7.1 Detect the correct group asset (project-specific)”

The group name varies per project. Do NOT hardcode TOFUIs.asset — detect:

Terminal window
# Pick an existing screen variant in Assets/Prefabs/UIs/ that already loads at runtime,
# read its .meta GUID, then grep the AddressableAssetsData groups:
existing_guid=$(head -3 'Assets/Prefabs/UIs/HomeScreenView Variant.prefab.meta' | grep guid | awk '{print $2}')
grep -l "$existing_guid" Assets/AddressableAssetsData/AssetGroups/*.asset
# The matching file is the group asset for screens/popups in this project.

In TheOneFeatureProject the result is UIs.asset. In other projects it may be TOFUIs.asset or <Project>UIs.asset.

mcp__UnityMCP__manage_asset does NOT expose an addressables_add action today (§10). Edit the group asset .asset file directly:

  1. Read the new prefab’s GUID from its .meta.

  2. Open the group asset YAML. Entries are sorted alphabetically by GUID under m_SerializeEntries.

  3. Find the insertion point by grepping for the first 1-2 hex chars of the new GUID. Insert in alphabetical order:

    - m_GUID: <new-prefab-guid>
    m_Address: <ScreenName>
    m_ReadOnly: 0
    m_SerializedLabels: []
    FlaggedDuringContentUpdateRestriction: 0
  4. The m_Address MUST equal the prefab basename without .prefab and match [ScreenInfo(nameof(MyScreenView))] on the script side.

mcp__UnityMCP__refresh_unity(scope="assets") so Unity picks up the group asset change.

Cross-reference: the t1k:unity:tof:addressables skill covers full TOF Addressables group taxonomy.

8. Final Checklist (before declaring done)

Section titled “8. Final Checklist (before declaring done)”
  • Prefab is a variant of BasePuzzleScreen.prefab or BasePuzzlePopup.prefab — file starts with !u!1001 & and ends with m_SourcePrefab: {fileID: 100100000, guid: <base-guid>}
  • Prefab name <ScreenName>View.prefab matches the future [ScreenInfo] key
  • Every [SerializeField] GameObject/Component field on the View has a matching child under Content (or Background/Foreground if explicitly placed there per §9)
  • Every UI child has explicit UnityEngine.RectTransform in components_to_add — never plain Transform (would warn under Canvas)
  • Every child’s RectTransform fields (anchor/pivot/sizeDelta/anchoredPos) were set via §9.2.5 post-create — verified by get_hierarchy readback or response transform.position
  • Every UI child was re-parented to FullScreenLayout / Background / Foreground via modify_contents(target, parent) — NOT left at prefab root
  • Every such field is wired on the View component via direct YAML fileID edit per §9.3 Track A — no null refs except documented <Widget>Prefab user-assigns. Post-wire verification: manage_prefabs(action="get_info") MUST return prefabType: "Variant", isVariant: true, parentPrefab: "<base>.prefab". If prefabType: "Prefab" the variant link is gone — restore from git and re-author.
  • Full-bleed art placed under Background (no SafeArea inset)
  • Main UI / popup body placed under Content (SafeArea-respecting)
  • Overlays / toasts placed under Foreground (no SafeArea inset)
  • Timeline_Grp intact; per-screen Appear/Disappear Timelines overridden on the variant if customized
  • No second SafeArea added; no CanvasScaler on root; no AspectRatioFitter on root
  • Every child’s pivot sits on the same edge as its anchor (§5)
  • Any widget used 2+ times is referenced as a prefab instance, not duplicated
  • Grids use FlexibleLayoutGroupVer2, not GridLayoutGroup
  • Prefab added to the correct Addressables group with address = prefab basename
  • refresh_unity succeeded with zero console errors
  • If layout image was provided: every text-on-button region has a nested <Button>/Label TMP child with m_text set + RT placement applied (§9.4.B)
  • If layout image was provided: every standalone text region has a Txt<Name> TMP child under FullScreenLayout (or Foreground) with m_text set + RT placement applied (§9.4.C)
  • No TMP child was spawned for text that ALREADY has a [SerializeField] TMP field on the View — those are covered by the §9.4.A field-driven path
  • Every TMP child has m_HorizontalAlignment and m_VerticalAlignment set per §9.4.E (not TMP’s left-top default m_HorizontalAlignment: 1 / m_VerticalAlignment: 256)

9. Materialize Content Children from View Fields (MANDATORY)

Section titled “9. Materialize Content Children from View Fields (MANDATORY)”

A skeleton variant with no children is incomplete — the Presenter’s OnViewReady will null-ref on this.View.BtnPause.onClick.AddListener(...) because BtnPause was never spawned. This section mandates field-driven child creation.

Parse the View class for [SerializeField] and [field: SerializeField] declarations. Capture for each field:

  • Field name (becomes child GameObject name)
  • Field type (drives §9.2 widget table)
  • Default value (for primitive fields — keep, no spawning)

Field-driven inspection is the INTERACTIVE-content driver (Buttons, Transforms, runtime-mutated widgets), but it is NOT the only driver. If a layout image is supplied, §9.4.B / §9.4.C ALSO spawn TMP decorations (text-on-button labels, standalone text badges) that have NO corresponding [SerializeField] field — purely visual content the Presenter never touches. Don’t suppress them just because the View doesn’t declare them.

9.2 Widget-type → child template mapping

Section titled “9.2 Widget-type → child template mapping”

For each field, create a child GameObject using this table. ALWAYS include UnityEngine.RectTransform in components_to_add — never let MCP default to plain Transform, which is invalid under a Canvas. (Verified 2026-05-28: create_child without explicit components_to_add ships a plain Transform.)

Field typecomponents_to_add (mandatory order)Default placement (applied via §9.2.5)
UnityEngine.UI.Button["UnityEngine.RectTransform", "UnityEngine.UI.Image", "UnityEngine.UI.Button"]Top-right HUD: anchor (1,1)-(1,1), pivot (1,1), size 120×120, anchoredPos (-40,-40)
UnityEngine.RectTransform / UnityEngine.Transform (container)["UnityEngine.RectTransform"] — for Transform fields too. If name contains Container/Group/Layout/Row/List, also add "UnityEngine.UI.HorizontalLayoutGroup"Bottom-stretched bar: anchor (0,0)-(1,0), pivot (0.5,0), sizeDelta (0,200), anchoredPos (0,200). For full-bleed parent containers like FeatureHolder / DefaultParent: anchor (0,0)-(1,1), pivot (0.5,0.5), sizeDelta (0,0), anchoredPos (0,0).
UnityEngine.UI.Image["UnityEngine.RectTransform", "UnityEngine.UI.Image"]Centered: anchor (0.5,0.5)-(0.5,0.5), pivot (0.5,0.5), size 200×200
TMPro.TMP_Text / TextMeshProUGUI (field-driven path only)["UnityEngine.RectTransform", "TMPro.TextMeshProUGUI"]Top: anchor (0,1)-(1,1), pivot (0.5,1), sizeDelta (0,80), anchoredPos (0,-40). Alignment defaults per §9.4.E (MANDATORY — never leave at TMP’s left-top default). For text-on-button OR standalone text NOT driven by a [SerializeField] field, see §9.4.B (nested Label) / §9.4.C (standalone Txt<Name>) — those are auto-spawned from the layout image without needing a field.
UnityEngine.UI.Slider["UnityEngine.RectTransform", "UnityEngine.UI.Slider"]Centered, size 300×40
UnityEngine.UI.ScrollRect["UnityEngine.RectTransform", "UnityEngine.UI.ScrollRect"] (then build Viewport + Content as separate create_child calls under it)Stretch: anchor (0,0)-(1,1), pivot (0.5,0.5), offsets 20
<CustomWidget> where the type IS a MonoBehaviour AND a project prefab named <CustomWidget>.prefab existscreate_child with source_prefab_path: "<path/to/<CustomWidget>.prefab>" instead of components_to_addInherit from prefab; offset to default placement
Field name ends in Prefab (e.g. BoosterButtonPrefab, ItemPrefab) — used by Presenter to spawn at runtimeDo NOT spawn a child. Document as user-assigns-in-Inspector.n/a
Primitive (string, int, float, bool, enum)Skip — not a GameObject refn/a

If a field has [Header("Background")] / [Header("Foreground")] attribute, override the parent from Content to that layer.

create_child’s parent_path argument is IGNORED (verified 2026-05-28) — every child lands at the prefab root regardless. Workaround: omit parent_path, then issue a separate modify_contents(target: "<ChildName>", parent: "<LayerName>") call to re-parent to FullScreenLayout (= Content), Background, or Foreground. See §10 for the exact pattern.

9.2.5 Apply RectTransform AFTER create_child (MANDATORY)

Section titled “9.2.5 Apply RectTransform AFTER create_child (MANDATORY)”

create_child does NOT set anchor/pivot/sizeDelta/anchoredPosition — the new child is born with default-zeros and Unity’s “centered, 100×100” implicit defaults. Without this step, every child stacks at the screen center regardless of the §9.2 default or §9.4 image-derived placement.

For EACH child spawned in §9.2, issue a second modify_contents call:

mcp__UnityMCP__manage_prefabs(
action="modify_contents",
prefab_path="<target>.prefab",
target="<ChildName>", # name lookup inside the prefab
parent="<LayerName>", # one of: FullScreenLayout / Background / Foreground (omit to keep at root)
component_properties={
"UnityEngine.RectTransform": {
"m_AnchorMin.x": <num>, "m_AnchorMin.y": <num>,
"m_AnchorMax.x": <num>, "m_AnchorMax.y": <num>,
"m_Pivot.x": <num>, "m_Pivot.y": <num>,
"m_AnchoredPosition.x": <num>, "m_AnchoredPosition.y": <num>,
"m_SizeDelta.x": <num>, "m_SizeDelta.y": <num>
}
}
)

Property name form is mandatory flat-axism_AnchorMin.x and m_AnchorMin.y as SEPARATE keys. The Vector2 object form m_AnchorMin: {x:0, y:0} returns Unsupported SerializedPropertyType: Vector2 (verified 2026-05-28). Same flat-axis rule for m_AnchorMax, m_Pivot, m_AnchoredPosition, m_SizeDelta.

Verification: after the call, the response includes transform.position reflecting anchoredPosition — quick sanity check. For deeper verify, call get_hierarchy and confirm the child path is <Root>/FullScreenLayout/<ChildName>.

9.3 Wire references on the View component (direct YAML — MCP gap workaround)

Section titled “9.3 Wire references on the View component (direct YAML — MCP gap workaround)”

MCP modify_contents(component_properties) cannot wire in-prefab object references today (verified 2026-05-28 against The1Studio/unity-mcp@beta). The resolver at ComponentOps.cs:719 routes {"name":...} payloads through ResolveSceneObjectByName which hardcodes scene-only lookup (ComponentOps.cs:875); the loaded prefab root from ManagePrefabs.ModifyContents:617 is never forwarded into the resolver. GameObjectLookup only falls back to PrefabStageUtility.GetCurrentPrefabStage() when a prefab is OPEN in Prefab Mode (GameObjectLookup.cs:164,292) — an in-memory LoadPrefabContents is invisible to that fallback. All three payload forms (name / instanceID / path) return scene-scoped errors on the prefab-asset path.

Use the direct YAML edit path below. It preserves the variant link (no Prefab-Mode-collapse risk) and works against any MCP version.

Procedure (Track A — direct YAML, MANDATORY today)

Section titled “Procedure (Track A — direct YAML, MANDATORY today)”
  1. Find each child component’s fileID. After §9.2 + §9.2.5 saved the prefab, parse the file with this awk script to map m_Name → component fileIDs:

    Terminal window
    awk '
    /^--- !u!1 &/ { cur_name="" }
    /^ m_Name: / { if (!cur_name) cur_name=$2; print "GO " cur_name }
    /^--- !u!114 &/ { comp=$3; sub("&","",comp); print " MB " comp " under " cur_name }
    /^--- !u!224 &/ { comp=$3; sub("&","",comp); print " RT " comp " under " cur_name }
    ' <prefab.prefab>

    Component type discrimination (when a child has multiple MonoBehaviours, e.g. Image + Button on a button): read 8 lines past the !u!114 &<fileID> anchor and match m_EditorClassIdentifier: — Button = UnityEngine.UI::UnityEngine.UI.Button, Image = UnityEngine.UI::UnityEngine.UI.Image, TMP = Unity.TextMeshPro::TMPro.TextMeshProUGUI. For Transform-typed View fields, use the !u!224 RectTransform fileID directly.

  2. Locate the View MonoBehaviour block. After §9.2’s components_to_add: ["<View>"] call, the View MB is appended near the end of the file as:

    --- !u!114 &<view-mb-fileID>
    MonoBehaviour:
    ...
    m_Script: {fileID: 11500000, guid: <ViewScript.cs.meta guid>, type: 3}
    m_EditorClassIdentifier: <Namespace>::<FullName>
    <field1>: {fileID: 0}
    <field2>: {fileID: 0}
    ...

    Every [SerializeField] GameObject/Component field starts as {fileID: 0}.

  3. Replace each {fileID: 0} with the matching component fileID via Edit tool. Map per the §9.2 widget-type column:

    Field typeWire to
    UnityEngine.UI.Buttonthe MB whose m_EditorClassIdentifier ends in UnityEngine.UI.Button (typically the 2nd MB after Image, per step 1’s identifier check)
    UnityEngine.UI.Imagethe MB whose m_EditorClassIdentifier ends in UnityEngine.UI.Image (typically the 1st MB)
    TMPro.TMP_Text / TextMeshProUGUITMP MB fileID
    UnityEngine.RectTransform / UnityEngine.TransformRectTransform (!u!224) fileID
    <CustomWidget> MonoBehaviourthe corresponding !u!114 fileID on the child
  4. Refresh + verify. Call mcp__UnityMCP__refresh_unity(scope="assets", mode="force"), then mcp__UnityMCP__manage_prefabs(action="get_info", prefab_path=...). Required readback: prefabType: "Variant" AND isVariant: true AND parentPrefab: "<base>.prefab". If prefabType: "Prefab" (standalone), the variant link is gone — restore from git and re-author per §1.

  5. Read console for errors. mcp__UnityMCP__read_console(types=["error"]) — must be 0 errors mentioning the View type or MissingReferenceException.

Track B — Prefab Mode opt-in (OPT-IN, collapses Track-B variants)

Section titled “Track B — Prefab Mode opt-in (OPT-IN, collapses Track-B variants)”

If you ACCEPT losing the variant link (becomes a standalone prefab — same Prefab-Mode-save-collapse failure mode as the existing Gotcha), you can drive Prefab Mode through MCP:

  • Open the prefab in Prefab Mode (currently no headless MCP action exists — manage_asset has no open action; execute_menu_item("Assets/Open Prefab Asset") requires Project-window selection which MCP cannot set). Practical reality: user must double-click the prefab in the Unity Editor to enter Prefab Mode.
  • While the stage is open: mcp__UnityMCP__manage_components(action="set_property", target="<RootName>", search_method="by_name", component_type="<ViewType>", properties={...}) — works because GameObjectLookup.SearchGameObjects falls back to PrefabStageUtility.GetCurrentPrefabStage() (verified GameObjectLookup.cs:164).
  • mcp__UnityMCP__manage_editor(action="close_prefab_stage") to save + exit.
  • Verify the prefab is NOT a variant anymore: get_info will return prefabType: "Prefab". Document the collapse in the commit message.

Use Track A unless you have a deliberate reason to abandon variant inheritance.

Upstream root cause (file the gap via /t1k:issue against The1Studio/unity-mcp@beta if it doesn’t already exist):

  • ComponentOps.SetObjectReference:719 → calls ResolveSceneObjectByName(prop, name, ...)
  • ResolveSceneObjectByName:861-887GameObjectLookup.SearchGameObjects(ByName, ...) → returns "No GameObject named 'X' found in scene." when no scene match (line 875)
  • ManagePrefabs.ModifyContents:617PrefabUtility.LoadPrefabContents(path) → does NOT enter Prefab Stage; the loaded root is never passed into ComponentOps
  • Proposed fix: overload SetObjectReference / ResolveSceneObjectByName to accept optional GameObject prefabRoot. When set, search prefabRoot.GetComponentsInChildren<Transform>(true) instead of the active scene.

If the user provided a layout image path (PNG/JPG/sketch) as a skill argument:

  1. Read the image with the Read tool — Claude’s multimodal vision handles PNG/JPG natively.
  2. Identify regions in the image:
    • Interactive widgets — buttons, sliders, scroll lists, HUD counters (drive §9.4.A field-driven mapping).
    • Text-inside-button labels — text rendered ON TOP of a button region (drive §9.4.B nested-label spawning).
    • Standalone text decorations — text NOT inside any button (titles, badges, subtitles, the “Normal” caption above a “Level 999” pill, etc.) (drive §9.4.C standalone TMP spawning).

§9.4.A — Map field-driven regions (interactive widgets)

Section titled “§9.4.A — Map field-driven regions (interactive widgets)”

For each [SerializeField] field on the View, map its name to the closest visual element in the image:

  • BtnPause → small button-shaped region; prefer pause-icon (||) if visible.
  • BoosterContainer → horizontal row of similar-sized cells, typically near the bottom.
  • CoinView / CurrencyView → top-right counter widget.
  • When the field type is RectTransform and image shows a grid of items, mark as FlexibleLayoutGroupVer2.

Override the §9.2 default placement table with image-derived anchors / pivots / sizes. Conversion formulas (image is Y-down, Unity anchor space is Y-up):

  • anchor.x = image_px_x / image_width
  • anchor.y = 1 - (image_px_y / image_height)
  • pivot.{x,y} MUST match anchor edge per §5 (top-left corner → pivot (0,1); bottom-center → pivot (0.5,0); etc.)
  • sizeDelta = approximate element width/height in pixels at the screen’s reference resolution from HyperCasualRootUI’s CanvasScaler (commonly 1080×1920 for portrait mobile).
  • anchoredPosition = offset from anchor point to pivot, in reference-resolution px.

Applied via §9.2.5 (these REPLACE the §9.2 defaults in the second modify_contents call). There is no separate “merge” step.

For any TMP field-driven child, ALSO apply TMP alignment per §9.4.E — default H=2 (Center) / V=512 (Middle) unless the image clearly indicates otherwise.

§9.4.B — Auto-spawn nested TMP labels for text-on-button

Section titled “§9.4.B — Auto-spawn nested TMP labels for text-on-button”

When the image shows text rendered ON TOP of a field-driven Button child (e.g. “Level 999” inside BtnPlay, “Levels” inside BtnLevelSelect):

  1. Skip if the View already declares a TMP field for the label (e.g. [SerializeField] TMP_Text btnPlayLabel). The §9.4.A field-driven path already covers that case — do NOT double-spawn.

  2. Create a NESTED child under the parent Button GO via two-step pattern (create + re-parent, per §9.2.5 + §10):

    mcp__UnityMCP__manage_prefabs(
    action="modify_contents",
    prefab_path="<target>.prefab",
    create_child=[{"name": "Label", "components_to_add": ["UnityEngine.RectTransform", "TMPro.TextMeshProUGUI"]}]
    )
    # then re-parent to the button:
    mcp__UnityMCP__manage_prefabs(
    action="modify_contents",
    prefab_path="<target>.prefab",
    target="Label",
    parent="BtnPlay"
    )

    Canonical name: Label. If the button shows multiple text regions, suffix: LabelTitle, LabelValue, etc.

  3. Apply RectTransform placement via §9.2.5. Default for centered text inside a button: anchor (0,0)-(1,1), pivot (0.5,0.5), sizeDelta (0,0), anchoredPos (0,0) — stretches to fill the button. For top-aligned subtitle (e.g. badge above a value): anchor (0,1)-(1,1), pivot (0.5,1), sizeDelta (0,40), anchoredPos (0,-8). Image-derived footprint overrides these defaults.

  4. Apply text content + alignment via modify_contents — batch text, fontSize, AND alignment in a single call per §9.4.E:

    mcp__UnityMCP__manage_prefabs(
    action="modify_contents",
    prefab_path="<target>.prefab",
    target="Label",
    component_properties={"TMPro.TextMeshProUGUI": {
    "m_text": "Level 999",
    "m_fontSize": 48,
    "m_HorizontalAlignment": 2,
    "m_VerticalAlignment": 512
    }}
    )

    The serialized field name is m_text (verified — see Common/Prefabs/titlePopup1.prefab for a wired example). Do NOT use text (public property) or m_Text (capitalised — wrong). Default alignment for nested text-on-button labels is H=2 (Center) / V=512 (Middle) — see §9.4.E for enum values and image-derived overrides.

  5. Naming collision guard: if <Button>/Label already exists (variant re-author case), SKIP spawn — preserve Inspector hand-edits.

§9.4.C — Auto-spawn standalone TMP children for non-button text

Section titled “§9.4.C — Auto-spawn standalone TMP children for non-button text”

When the image shows text NOT inside any field-driven Button region (titles, badges, captions like the “Normal” label above a “Level 999” pill):

  1. Skip if the View already declares a TMP field for it (same dedup rule as §9.4.B).

  2. Create a child under FullScreenLayout (Content) — same two-step create + re-parent pattern. Parent is FullScreenLayout by default; use Foreground if the image shows the text floats above other UI (toast-style overlay).

    Naming: Txt<CamelCaseFromText> (e.g. text “Normal” → TxtNormal; “Level Complete!” → TxtLevelComplete). If text exceeds 20 chars, truncate to a representative slug (e.g. TxtDescription).

  3. Apply RectTransform placement per the image-derived footprint via §9.2.5 (same rules as §9.4.A).

  4. Apply text content + alignment via the same m_text + m_fontSize + alignment batched modify_contents call as §9.4.B step 4. Alignment for standalone TMP is image-derived (not always Center+Middle) — pick H/V from the §9.4.E lookup table based on where the text sits within its parent bounding box in the layout image (e.g. caption above pill = H=2/V=256 (Top); centered title = H=2/V=512 (Middle); footer subtitle = H=2/V=1024 (Bottom)).

  5. Default font size: derive from image text height relative to reference resolution. Heuristic: m_fontSize = round(image_text_pixel_height * (reference_width / image_width)) where reference_width comes from HyperCasualRootUI’s CanvasScaler (typically 1080 for portrait mobile). Typical values 36-72. User can adjust in Inspector if needed.

  6. Naming collision guard: same as §9.4.B step 5.

§9.4.D — Echo the full inferred layout (MANDATORY before any apply)

Section titled “§9.4.D — Echo the full inferred layout (MANDATORY before any apply)”

Before applying any of §9.4.A/B/C, echo the COMPLETE detected layout back to the user — interactive widgets AND text decorations — as a single confirmation block:

“From the image I see:

  • Field-driven (§9.4.A):
    • BtnProfile top-left HUD (~70px from edges)
    • BtnSetting top-right HUD (~70px from edges)
    • BtnPlay bottom-center pill (~500×200, y=140)
    • BtnLevelSelect bottom-left square (~160×160, y=160)
    • FeatureHolder, DefaultParent full-bleed containers
  • Text-on-button labels (§9.4.B):
    • BtnPlay/Label text “Level 999” centered, stretched
    • BtnLevelSelect/Label text “Levels” bottom-aligned
  • Standalone text (§9.4.C):
    • TxtNormal text “Normal” above BtnPlay (~y=180 from bottom)

Apply? (yes / adjust)”

Wait for user confirmation. If user adjusts, capture the adjustments and re-apply.

If no image is provided, only §9.4.A defaults apply via §9.2.5 with no echo step. §9.4.B/C are no-ops without an image source.

§9.4.E — Apply TMP alignment (MANDATORY)

Section titled “§9.4.E — Apply TMP alignment (MANDATORY)”

Every TMP child spawned by §9.2 (widget-table TMP row), §9.4.A (field-driven), §9.4.B (nested label), or §9.4.C (standalone) MUST have explicit m_HorizontalAlignment and m_VerticalAlignment set. TMP children spawn with left-top defaults (m_HorizontalAlignment: 1, m_VerticalAlignment: 256) that almost never match the layout image — text-on-button labels then render upper-left of the button rect instead of centered.

Enum values (serialized — write these integers directly into component_properties):

PropertyValueMeaning
m_HorizontalAlignment1Left
m_HorizontalAlignment2Center
m_HorizontalAlignment4Right
m_HorizontalAlignment8Justified
m_HorizontalAlignment16Flush
m_HorizontalAlignment32Geometry Center
m_VerticalAlignment256Top
m_VerticalAlignment512Middle
m_VerticalAlignment1024Bottom
m_VerticalAlignment2048Baseline
m_VerticalAlignment4096Midline
m_VerticalAlignment8192Capline

Defaults by section:

SourceDefault HDefault VOverride rule
§9.2 widget-table TMP row2 (Center)512 (Middle)Image-derived per §9.4.A if image provided
§9.4.A field-driven2 (Center)512 (Middle)Override only if image clearly indicates otherwise
§9.4.B nested label (text-on-button)2 (Center)512 (Middle)Hard default — text-on-button is almost always center+middle
§9.4.C standalone TMPimage-derived (see lookup below)image-derivedAlways image-derived; no fixed default

§9.4.C image-derived lookup heuristic:

Locate the text region within its parent bounding box in the layout image. Pick H/V from this table:

Image position within parentHV
Caption ABOVE a pill / panel (top of parent)2 (Center)256 (Top)
Centered title / banner (middle of parent)2 (Center)512 (Middle)
Footer subtitle / fine print (bottom of parent)2 (Center)1024 (Bottom)
Left-aligned header / section title1 (Left)256 (Top)
Right-aligned counter / badge value4 (Right)512 (Middle)
Inline numeric value flush against right edge4 (Right)512 (Middle)
Bottom-left corner stamp / timestamp1 (Left)1024 (Bottom)
Bottom-right corner stamp / count4 (Right)1024 (Bottom)

For diagonal/intermediate positions not in the table, snap to the nearest row.

Exact MCP call pattern (batched — alignment goes with text + fontSize in a single modify_contents):

mcp__UnityMCP__manage_prefabs(
action="modify_contents",
prefab_path="<target>.prefab",
target="<TmpChildName>",
component_properties={
"TMPro.TextMeshProUGUI": {
"m_text": "<text from image>",
"m_fontSize": <derived size>,
"m_HorizontalAlignment": <enum int from table above>,
"m_VerticalAlignment": <enum int from table above>
}
}
)

Do NOT set m_textAlignment — that is the LEGACY combined enum (TMP serializes it as 65535 by default and ignores it once m_HorizontalAlignment / m_VerticalAlignment are set). Leave m_textAlignment untouched.

Gates relevant to §9 (see §8 for the full list):

  • Every [SerializeField] GameObject/Component field has a matching child
  • Every UI child has explicit UnityEngine.RectTransform in components_to_add (never plain Transform)
  • Every child’s RectTransform (anchor/pivot/sizeDelta/anchoredPos) was set via §9.2.5 — verified via get_hierarchy readback or response transform.position
  • Every UI child was re-parented to FullScreenLayout / Background / Foreground via §9.2.5’s parent: arg (not at prefab root)
  • Every such field is wired on the View component via direct YAML fileID edit per §9.3 Track A — no null refs except documented <Widget>Prefab user-assigns; post-wire get_info confirms isVariant: true

Quick-reference status as of 2026-05-28 against The1Studio/unity-mcp@beta. Re-test before assuming a row is still accurate.

OperationMCP callStatusNotes
Instantiate prefab in scenemanage_gameobject(action="create", prefab_path=…)✅ WorksUse this to bring base into temp scene
Add component to instancemanage_gameobject(action="modify", components_to_add=[…])✅ WorksAdd View MonoBehaviour here
Save GameObject as standalone prefabmanage_prefabs(action="create_from_gameobject", unlink_if_instance=true)✅ WorksLoses variant link — not what §1 wants
Save GameObject as prefab VARIANTmanage_prefabs(action="create_from_gameobject", variant=true)❌ Schema lacks variant paramUse §1 Track B (minimal-YAML fallback)
Read prefab hierarchymanage_prefabs(action="get_hierarchy", prefab_path=…)⚠️ Assets/ onlyErrors on Packages/… paths
Modify prefab contents — create childmanage_prefabs(action="modify_contents", create_child=[…])✅ WorksDefault GO has plain Transform. ALWAYS pass components_to_add: ["UnityEngine.RectTransform", …] for UI children.
create_child’s parent_path argcreate_child=[{name, parent_path, …}]❌ IGNOREDChildren land at prefab root regardless. Workaround: separate modify_contents(target, parent) call (next row).
Re-parent existing childmodify_contents(target="<ChildName>", parent="<LayerName>")✅ WorksName lookup works; parent is a sibling/layer name. Use to move under FullScreenLayout / Background / Foreground.
Set RectTransform anchor/pivot/sizemodify_contents(component_properties={"UnityEngine.RectTransform": {"m_AnchorMin.x": …, …}})✅ Works with flat-axis formVector2 form (m_AnchorMin: {x,y}) returns Unsupported SerializedPropertyType. Use m_AnchorMin.x + m_AnchorMin.y as separate keys.
Upgrade plain Transform → RectTransformmodify_contents(target, components_to_add=["UnityEngine.RectTransform"])✅ WorksReplaces the Transform in-place (RectTransform inherits). Safe on FeatureHolder/DefaultParent.
Wire in-prefab object reference (headless)`modify_contents(component_properties={ViewType: {field: {“nameinstanceIDpath”: …, “component”: …}}})`
Wire in-prefab object reference (Prefab Mode)manage_components(action="set_property", search_method="by_name", target="<Root>", component_type="<View>", properties={...}) while Prefab Mode is open⚠️ Works but COLLAPSES Track-B variantsGameObjectLookup falls back to PrefabStageUtility.GetCurrentPrefabStage() (GameObjectLookup.cs:164). Save-on-close rewrites the file as a standalone (!u!1 not !u!1001). Use only if you accept losing the variant link. See §9.3 Track B.
Set component property (top-level tool)manage_components(action="set_property")⚠️ Scene-only + tool deferredOnly resolves live scene objects, not prefab-asset internals. For prefab assets use modify_contents.
Add asset to Addressables groupmanage_asset(action="addressables_add")❌ Not in schemaEdit the *.asset YAML directly (§7.2)
Force re-importrefresh_unity(scope="assets", mode="force")✅ WorksCall after any direct-YAML edit
Read console for errorsread_console(types=["error"])✅ WorksFilter by message text via filter_text
Find GameObjects in scenefind_gameobjects(search_term=…)✅ WorksUse for cleanup-after-instantiate
Search assets by namemanage_asset(action="search", search_pattern=…)⚠️ InconsistentSometimes returns 0 for clearly-existing assets; fall back to Bash find for filesystem-level lookup

When a row marked ❌ blocks progress: file a [t1k:mcp-gap] marker per rules/workflow-failure-auto-issue.md against The1Studio/unity-mcp.

Authoring of .prefab assets under Packages/TheOneFeature.PuzzleUI/, Packages/TheOneFeature/UI/, and Assets/Prefabs/UIs/. Does NOT cover: View/Presenter C# scripts, [ScreenInfo] attribute wiring, VContainer registration, or runtime open/close calls — those are handled by the implementation skills.

  • Layout-image text decorations are auto-spawned even without a [SerializeField] field. Text inside a field-driven button (e.g. “Level 999” inside BtnPlay) becomes a nested <Button>/Label TMP child (§9.4.B); standalone text (e.g. “Normal” caption above a pill) becomes a Txt<Name> child under FullScreenLayout (§9.4.C). Both get full RT placement per §9.2.5 + m_text content from the image. They are NOT wired to the View script — the Presenter never touches them, they are purely visual. User can later promote to [SerializeField] if interactivity is added. Without this, image-driven UIs ship with empty buttons and missing labels (failure mode observed 2026-05-28 on HomeScreenView authoring run).
  • TMP serialized text property is m_text, NOT text or m_Text. Verified against Packages/TheOneFeature.PuzzleUI/Common/Prefabs/titlePopup1.prefab. The text form is the runtime C# property (TMP_Text.text); the m_Text form (capitalised M) is wrong. MCP modify_contents(component_properties={"TMPro.TextMeshProUGUI": {"m_text": "<value>"}}) is the only working form.
  • TMP children spawn with left-top alignment by default. New TMPro.TextMeshProUGUI components serialize with m_HorizontalAlignment: 1 (Left) and m_VerticalAlignment: 256 (Top). Without §9.4.E setting H=2 (Center) / V=512 (Middle), text-on-button labels like “Level 999” or “Levels” render in the upper-left of the button rect instead of visually centered as the layout image shows. Observed 2026-05-28 on HomeScreenView authoring (LabelPlay, LabelLevelSelect misaligned). The alignment property names are m_HorizontalAlignment / m_VerticalAlignment — NOT m_textAlignment (that field is the legacy combined enum, serialized as 65535 by default; leave it at default and set the two split-axis fields instead). Enum values + image-derived heuristics: §9.4.E.
  • Headless field-wiring via modify_contents(component_properties) does NOT work for in-prefab object references. Verified 2026-05-28: ALL three payload forms — {"name":...}, {"instanceID":...}, {"path":...} — fail with scene-scoped errors when targeting children inside a prefab ASSET (not a Prefab Stage). Root cause: ComponentOps.SetObjectReference:719 routes name lookups through ResolveSceneObjectByName which hardcodes scene search at ComponentOps.cs:875; the prefab root loaded by ManagePrefabs.ModifyContents:617 via PrefabUtility.LoadPrefabContents is never forwarded into the resolver. GameObjectLookup only falls back to PrefabStageUtility.GetCurrentPrefabStage() when a prefab is OPEN in Prefab Mode (GameObjectLookup.cs:164,292) — an in-memory load is invisible to that fallback. Workaround: direct YAML edit of <field>: {fileID: <ComponentFileID>} on the View’s MonoBehaviour block (§9.3 Track A). Preserves variant link, works against any MCP version. Track B (Prefab Mode + manage_components.set_property) does work but collapses Track-B variants to standalone — use only if you accept losing the variant link.
  • create_child ignores parent_path. Verified 2026-05-28: regardless of the value passed, the new child lands at the prefab root. Always follow with a separate modify_contents(target: "<ChildName>", parent: "<LayerName>") call to re-parent. The skill checklist gates this.
  • create_child defaults to plain Transform. Without explicit components_to_add, the new GameObject ships with UnityEngine.Transform — invalid under a Canvas (Unity warns + breaks layout). ALWAYS pass components_to_add: ["UnityEngine.RectTransform", …] for UI children (§9.2). If you forgot, recover via modify_contents(target, components_to_add: ["UnityEngine.RectTransform"]) — this replaces the Transform in-place because RectTransform inherits.
  • RectTransform Vector2 properties need flat-axis form. The MCP serializer rejects m_AnchorMin: {x: 0, y: 1} with Unsupported SerializedPropertyType: Vector2. Use separate keys m_AnchorMin.x and m_AnchorMin.y. Same for m_AnchorMax, m_Pivot, m_AnchoredPosition, m_SizeDelta.
  • Children without §9.2.5 position step stack at center. create_child does NOT set anchor/pivot/sizeDelta/anchoredPosition — the new GO uses Unity’s implicit “centered 100×100” defaults regardless of the §9.2 default placement column. The two-step pattern (create_child then modify_contents(component_properties.RectTransform)) is MANDATORY, not advisory.
  • create_from_gameobject produces a STANDALONE prefab, not a variant. The MCP tool’s schema has no variant flag (verified 2026-05-28). Use §1 Track B minimal-YAML fallback until upstream The1Studio/unity-mcp adds the parameter. Symptom of the silent failure: resulting .prefab starts with --- !u!1 &<id>\nGameObject: instead of --- !u!1001 &<id>\nPrefabInstance:.
  • Prefab-Mode save collapses a Track B variant to standalone. Verified 2026-05-28: if you instantiate the Track B variant in scene Prefab Mode, add structural changes (new children), then close_prefab_stage (or any Unity-side save), Unity rewrites the file as a fully-fleshed !u!1 GameObject prefab — the m_SourcePrefab link is gone. To avoid: skip Prefab Mode entirely for structural changes — use mcp__UnityMCP__manage_prefabs.modify_contents(create_child=[...], component_properties=...) directly against the asset path. modify_contents preserves variant overrides because it operates on the prefab asset, not a scene instance.
  • Addressables group name is project-specific. Older skill versions hardcoded TOFUIs.asset; the TheOneFeatureProject actually uses UIs.asset. Always detect via §7.1 grep, never assume.
  • Skeleton prefab without children → runtime NullReferenceException. Skipping §9 leaves [SerializeField] refs unwired; the Presenter’s OnViewReady will null-ref on first access. §9 is MANDATORY, not advisory.
  • Field-name *Prefab fields are user-assigns, not children. BoosterButtonPrefab, ItemPrefab, etc. are prefab references the Presenter uses for runtime spawning — they MUST NOT be materialized as children. Skill emits a “user-assigns this in Inspector” note instead.
  • Layout image hallucination. When using §9.4, the skill MUST echo its interpretation to the user before applying. Don’t silently spawn children from a guessed layout — show the parse and wait for confirmation.
  • Variant-of-variant root-GO fileID lookup. For deep variant chains (e.g. BasePuzzleScreen → BaseUIScreen), the root GO fileID in the variant’s target: lines points to the grandparent’s GO, not the immediate parent’s. See §1.5 Case B.
  • Duplicating the base instead of variant-ing it — duplicated screens stop receiving base-prefab fixes. Always Track A→B for true variant.
  • Prefab name = Addressables address = [ScreenInfo] key. Drift between the three is the #1 silent-failure mode. Always use nameof(MyScreenView) on the script side.
  • Full-bleed background placed under Content — SafeArea insets it on notched devices, revealing letterboxing. Background images go under Background.
  • Tap-targets placed under Background or Foreground — they can fall under the notch or gesture bar. Interactive UI MUST live under Content.
  • Deleting Timeline_Grp — open/close transitions silently no-op. Never delete it.
  • Editing the base’s Timeline assets directly — changes every screen. Author per-screen Timeline assets and override the binding on the variant.
  • Adding a second SafeArea — double inset. The base ships exactly one on Content — leave it as the only one.
  • CanvasScaler / AspectRatioFitter on the variant root — fights HyperCasualRootUI’s scaler. Never add either to the variant.
  • GridLayoutGroup for responsive grids — fixed cell count breaks on landscape/portrait. Use FlexibleLayoutGroupVer2 with BaseOnContent preset.
  • Backdrop above PopupBody (popup variant) — Backdrop renders on top, hiding the popup. Backdrop must be the FIRST child of its parent.
  • Pivot not matching anchor edge — element drifts on different resolutions. Apply §5 rigidly.
  • Renaming a prefab after Addressables registration without updating the address — Addressables does NOT auto-rename. Re-edit the group asset entry after any rename.
  • Hand-editing .prefab YAML beyond the §1 Track B template — corrupts variant overrides silently. The Track B template is a fixed pattern; do not improvise inside it.