From b09bb53651d250fac2e44114c3d38276f01764d6 Mon Sep 17 00:00:00 2001 From: hatayama Date: Thu, 18 Jun 2026 19:37:17 +0900 Subject: [PATCH] update skills --- .agents/skills/link-go-cli/SKILL.md | 81 ---- .agents/skills/uloop-clear-console/SKILL.md | 3 +- .../skills/uloop-control-play-mode/SKILL.md | 7 +- .../uloop-execute-dynamic-code/SKILL.md | 6 +- .../references/asset-operations.md | 47 +- .../references/cleanup-operations.md | 95 +---- .../references/playmode-ui-controls.md | 41 +- .../references/scene-operations.md | 7 - .../references/scriptableobject.md | 27 +- .../references/selection-operations.md | 63 +-- .../skills/uloop-find-game-objects/SKILL.md | 2 +- .agents/skills/uloop-focus-window/SKILL.md | 8 - .agents/skills/uloop-get-logs/SKILL.md | 7 +- .agents/skills/uloop-launch/SKILL.md | 38 +- .agents/skills/uloop-record-input/SKILL.md | 5 +- .agents/skills/uloop-replay-input/SKILL.md | 5 +- .agents/skills/uloop-run-tests/SKILL.md | 4 +- .agents/skills/uloop-screenshot/SKILL.md | 3 - .../skills/uloop-simulate-keyboard/SKILL.md | 14 +- .../uloop-simulate-mouse-input/SKILL.md | 23 +- .../skills/uloop-simulate-mouse-ui/SKILL.md | 10 +- .../uloop-wait-for-pause-point/SKILL.md | 1 - .claude/commands/link-go-cli.md | 69 --- .claude/rules/cli.md | 98 ----- .claude/skills/find-orphaned-meta/SKILL.md | 46 -- .../scripts/find-orphaned-meta.sh | 34 -- .claude/skills/simulate-mouse-demo/SKILL.md | 114 ----- .claude/skills/uloop-clear-console/SKILL.md | 3 +- .../skills/uloop-control-play-mode/SKILL.md | 7 +- .../uloop-execute-dynamic-code/SKILL.md | 6 +- .../references/asset-operations.md | 47 +- .../references/cleanup-operations.md | 95 +---- .../references/playmode-ui-controls.md | 41 +- .../references/scene-operations.md | 7 - .../references/scriptableobject.md | 27 +- .../references/selection-operations.md | 63 +-- .../skills/uloop-find-game-objects/SKILL.md | 2 +- .claude/skills/uloop-focus-window/SKILL.md | 8 - .claude/skills/uloop-get-logs/SKILL.md | 7 +- .claude/skills/uloop-launch/SKILL.md | 38 +- .claude/skills/uloop-record-input/SKILL.md | 5 +- .claude/skills/uloop-replay-input/SKILL.md | 5 +- .claude/skills/uloop-run-tests/SKILL.md | 4 +- .claude/skills/uloop-screenshot/SKILL.md | 3 - .../skills/uloop-simulate-keyboard/SKILL.md | 14 +- .../uloop-simulate-mouse-input/SKILL.md | 23 +- .../skills/uloop-simulate-mouse-ui/SKILL.md | 10 +- .../uloop-wait-for-pause-point/SKILL.md | 1 - .codex/skills/uloop-clear-console/SKILL.md | 49 --- .codex/skills/uloop-compile/SKILL.md | 50 --- .../skills/uloop-control-play-mode/SKILL.md | 65 --- .../uloop-execute-dynamic-code/SKILL.md | 88 ---- .../references/asset-operations.md | 194 --------- .../references/batch-operations.md | 399 ----------------- .../references/cleanup-operations.md | 403 ------------------ .../references/material-operations.md | 172 -------- .../playmode-automation-powershell.md | 342 --------------- .../references/playmode-automation-zsh.md | 299 ------------- .../references/playmode-inspection.md | 151 ------- .../references/playmode-ui-controls.md | 190 --------- .../references/prefab-operations.md | 221 ---------- .../references/scene-operations.md | 238 ----------- .../references/scriptableobject.md | 216 ---------- .../references/selection-operations.md | 302 ------------- .../references/undo-operations.md | 210 --------- .../skills/uloop-find-game-objects/SKILL.md | 90 ---- .codex/skills/uloop-focus-window/SKILL.md | 46 -- .codex/skills/uloop-get-hierarchy/SKILL.md | 62 --- .codex/skills/uloop-get-logs/SKILL.md | 64 --- .codex/skills/uloop-hello-world/SKILL.md | 53 --- .codex/skills/uloop-launch/SKILL.md | 68 --- .codex/skills/uloop-record-input/SKILL.md | 55 --- .../references/deterministic-replay.md | 15 - .codex/skills/uloop-replay-input/SKILL.md | 61 --- .../references/deterministic-replay.md | 15 - .codex/skills/uloop-run-tests/SKILL.md | 78 ---- .codex/skills/uloop-screenshot/SKILL.md | 97 ----- .../references/annotated-elements.md | 37 -- .../skills/uloop-simulate-keyboard/SKILL.md | 108 ----- .../uloop-simulate-mouse-input/SKILL.md | 124 ------ .../skills/uloop-simulate-mouse-ui/SKILL.md | 143 ------- .../uloop-wait-for-pause-point/SKILL.md | 92 ---- 82 files changed, 192 insertions(+), 5879 deletions(-) delete mode 100644 .agents/skills/link-go-cli/SKILL.md delete mode 100644 .claude/commands/link-go-cli.md delete mode 100644 .claude/rules/cli.md delete mode 100644 .claude/skills/find-orphaned-meta/SKILL.md delete mode 100755 .claude/skills/find-orphaned-meta/scripts/find-orphaned-meta.sh delete mode 100644 .claude/skills/simulate-mouse-demo/SKILL.md delete mode 100644 .codex/skills/uloop-clear-console/SKILL.md delete mode 100644 .codex/skills/uloop-compile/SKILL.md delete mode 100644 .codex/skills/uloop-control-play-mode/SKILL.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/SKILL.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/asset-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/batch-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/cleanup-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/material-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/playmode-inspection.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/prefab-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/scene-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/scriptableobject.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/selection-operations.md delete mode 100644 .codex/skills/uloop-execute-dynamic-code/references/undo-operations.md delete mode 100644 .codex/skills/uloop-find-game-objects/SKILL.md delete mode 100644 .codex/skills/uloop-focus-window/SKILL.md delete mode 100644 .codex/skills/uloop-get-hierarchy/SKILL.md delete mode 100644 .codex/skills/uloop-get-logs/SKILL.md delete mode 100644 .codex/skills/uloop-hello-world/SKILL.md delete mode 100644 .codex/skills/uloop-launch/SKILL.md delete mode 100644 .codex/skills/uloop-record-input/SKILL.md delete mode 100644 .codex/skills/uloop-record-input/references/deterministic-replay.md delete mode 100644 .codex/skills/uloop-replay-input/SKILL.md delete mode 100644 .codex/skills/uloop-replay-input/references/deterministic-replay.md delete mode 100644 .codex/skills/uloop-run-tests/SKILL.md delete mode 100644 .codex/skills/uloop-screenshot/SKILL.md delete mode 100644 .codex/skills/uloop-screenshot/references/annotated-elements.md delete mode 100644 .codex/skills/uloop-simulate-keyboard/SKILL.md delete mode 100644 .codex/skills/uloop-simulate-mouse-input/SKILL.md delete mode 100644 .codex/skills/uloop-simulate-mouse-ui/SKILL.md delete mode 100644 .codex/skills/uloop-wait-for-pause-point/SKILL.md diff --git a/.agents/skills/link-go-cli/SKILL.md b/.agents/skills/link-go-cli/SKILL.md deleted file mode 100644 index 67a516fa60..0000000000 --- a/.agents/skills/link-go-cli/SKILL.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -name: link-go-cli -description: Link this repository's local Go uloop CLI as the global `uloop` command. Use when the user asks to link, relink, restore, or verify the Go CLI from this checkout, especially for `scripts/use-local-uloop.sh` or replacing old `npm link` workflows. ---- - -# Link Go CLI - -## Overview - -Link the current checkout's native Go CLI so `uloop` on PATH resolves to the development binary under `cli/dist/.../uloop`. - -## Workflow - -### 1. Confirm Repository State - -Run from the repository root and confirm the Go CLI layout exists: - -```bash -git rev-parse --show-toplevel -git status --short --branch -test -f cli/go.mod -test -x scripts/check-go-cli.sh -test -x scripts/use-local-uloop.sh -``` - -If the repository root is different from the visible cwd, switch to the actual root before continuing. - -### 2. Verify the Go CLI - -Run: - -```bash -scripts/check-go-cli.sh -``` - -Do not continue to linking if formatting, vet, lint, or tests fail. - -If the script fails because local development binaries are missing, run `scripts/build-go-cli.sh`, then rerun `scripts/check-go-cli.sh`. - -### 3. Link the Local CLI - -Use the repository script instead of hand-writing the symlink: - -```bash -scripts/use-local-uloop.sh link -``` - -The script selects the current platform binary: - -- `Darwin` + `arm64` or `aarch64`: `cli/dist/darwin-arm64/uloop` -- `Darwin` + `x86_64` or `amd64`: `cli/dist/darwin-amd64/uloop` - -If the current platform does not match one of these combinations, stop without linking and report that this repository does not include a local development binary for the current platform. - -The script backs up an existing global `uloop` as `uloop.before-local-link` before creating the symlink. - -### 4. Inspect or Restore - -Use: - -```bash -scripts/use-local-uloop.sh status -scripts/use-local-uloop.sh restore -``` - -### 5. Verify the Link - -Run: - -```bash -which uloop -readlink "$(which uloop)" -uloop --version -uloop --help -``` - -The `readlink` result must point at this checkout's `cli/dist/.../uloop`. - -## Completion Report - -Report the `which uloop` path, symlink target, `uloop --version`, `scripts/check-go-cli.sh` result, and whether a backup exists. diff --git a/.agents/skills/uloop-clear-console/SKILL.md b/.agents/skills/uloop-clear-console/SKILL.md index 5c633ca30b..3cb9e2be3f 100644 --- a/.agents/skills/uloop-clear-console/SKILL.md +++ b/.agents/skills/uloop-clear-console/SKILL.md @@ -45,5 +45,4 @@ Returns JSON with: - `errorCount` (number): Errors cleared - `warningCount` (number): Warnings cleared - `logCount` (number): Info logs cleared -- `message` (string): Description of the result; carries the failure summary when the operation fails (e.g. `"Failed to clear console: ..."`) -- `errorMessage` (string): Currently always empty for this tool — read `message` for failure details +- `message` (string): Description of the result. On failure, this field carries the error summary. diff --git a/.agents/skills/uloop-control-play-mode/SKILL.md b/.agents/skills/uloop-control-play-mode/SKILL.md index 662c027629..bfdb9e9bc9 100644 --- a/.agents/skills/uloop-control-play-mode/SKILL.md +++ b/.agents/skills/uloop-control-play-mode/SKILL.md @@ -57,9 +57,6 @@ Returns JSON with the current play mode state: ## Notes -- Play action starts the game in the Unity Editor (also resumes from pause) -- Stop action exits play mode and returns to edit mode. If Play Mode was already stopped, `changed` is `false`, `wasAlreadyStopped` is `true`, and `message` is `Play mode was already stopped`. -- Pause action pauses the game while remaining in play mode -- Step action advances exactly one frame and leaves play mode paused (the Editor's Next Frame button; independent of Time.timeScale). Requires PlayMode to be running; repeat to walk transitions frame by frame -- Useful for automated testing workflows +- Stop on an already-stopped Editor sets `changed: false`, `wasAlreadyStopped: true` +- Step is independent of `Time.timeScale`; requires PlayMode to be running - The command waits for the requested state before returning. Increase `--timeout-seconds` for projects with slow PlayMode entry. diff --git a/.agents/skills/uloop-execute-dynamic-code/SKILL.md b/.agents/skills/uloop-execute-dynamic-code/SKILL.md index 45ce0d1acc..1b2c08ebf9 100644 --- a/.agents/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.agents/skills/uloop-execute-dynamic-code/SKILL.md @@ -18,8 +18,7 @@ This tool can inspect reachable Unity state, such as GameObjects, components, pu 1. Read the relevant reference file(s) from the Code Examples section below 2. Construct C# code based on the reference examples 3. Execute with `--code` or `--code-file` using the active shell's quoting guidance -4. If execution fails, adjust code and retry -5. Report the execution result +4. Report the execution result ## Parameters @@ -48,8 +47,7 @@ Returns JSON: - `result`: string — value of the snippet's `return` statement (empty when omitted) - `logs`: string[] — execution messages from the dynamic-code tool; read Unity Console `Debug.Log` output with `get-logs` - `compilationErrors`: object[] — Roslyn diagnostics with `message`, `line`, `column`, `errorCode`, optional `hint` and `suggestions` -- `errorMessage`: string — top-level failure summary (empty on success) -- `error`: string — alias of `errorMessage` +- `error` / `errorMessage`: string — top-level failure summary (empty on success) - `updatedCode`: string|null — the wrapped form actually compiled (handy when debugging using-statement reordering) - `diagnosticsSummary`: string|null — compact summary when diagnostics are available - `diagnostics`: object[] — structured diagnostics; same shape as `compilationErrors`, usually populated together with it diff --git a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md index 31ddf7adc1..0dfc3876b9 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -2,54 +2,25 @@ Code examples for AssetDatabase operations using `execute-dynamic-code`. -## Find Assets by Type +## Find Assets ```csharp using UnityEditor; using System.Collections.Generic; -string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab"); -List paths = new List(); - -foreach (string guid in prefabGuids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} prefabs"; -``` - -## Find Assets by Name +// By type +string[] guids = AssetDatabase.FindAssets("t:Prefab"); +// By name +// string[] guids = AssetDatabase.FindAssets("Player"); +// By type in folder +// string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Prefabs" }); -```csharp -using UnityEditor; -using System.Collections.Generic; - -string searchName = "Player"; -string[] guids = AssetDatabase.FindAssets(searchName); List paths = new List(); - -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} assets matching '{searchName}'"; -``` - -## Find Assets in Folder - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string folder = "Assets/Prefabs"; -string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { folder }); -List paths = new List(); - foreach (string guid in guids) { paths.Add(AssetDatabase.GUIDToAssetPath(guid)); } -return $"Found {paths.Count} prefabs in {folder}"; +return $"Found {paths.Count} assets"; ``` ## Duplicate Asset @@ -76,7 +47,7 @@ string error = AssetDatabase.MoveAsset(sourcePath, destPath); return string.IsNullOrEmpty(error) ? $"Moved to {destPath}" : $"Error: {error}"; ``` -## Rename Asset +## Rename Asset (no Undo) ```csharp using UnityEditor; diff --git a/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md index 40eec9a52f..1c2e023f0a 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md @@ -78,56 +78,27 @@ Undo.CollapseUndoOperations(undoGroup); return $"Removed {totalRemoved} missing scripts from scene"; ``` -## Detect Missing References in Component +## Scan for Missing References + +Scans the entire scene by default. Set `scanSelectionOnly` to `true` only when the task asks for the selected GameObject. ```csharp using UnityEditor; +bool scanSelectionOnly = false; GameObject selected = Selection.activeGameObject; -if (selected == null) +if (scanSelectionOnly && selected == null) { return "No GameObject selected"; } -List missingRefs = new List(); - -Component[] components = selected.GetComponents(); -foreach (Component comp in components) -{ - if (comp == null) continue; - - SerializedObject so = new SerializedObject(comp); - SerializedProperty prop = so.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - missingRefs.Add($"{comp.GetType().Name}.{prop.name}"); - } - } - } -} +GameObject[] targets = scanSelectionOnly + ? new[] { selected } + : Object.FindObjectsByType(FindObjectsSortMode.None); -if (missingRefs.Count == 0) -{ - return "No missing references found"; -} - -return $"Missing references: {string.Join(", ", missingRefs)}"; -``` - -## Scan Scene for Missing References - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); List results = new List(); -foreach (GameObject obj in allObjects) +foreach (GameObject obj in targets) { Component[] components = obj.GetComponents(); foreach (Component comp in components) @@ -152,10 +123,10 @@ foreach (GameObject obj in allObjects) if (results.Count == 0) { - return "No missing references found in scene"; + return "No missing references found"; } -return $"Missing references ({results.Count}): {string.Join(", ", results.Take(10))}..."; +return $"Missing references ({results.Count}): {string.Join(", ", results.Take(10))}"; ``` ## Find Unused Materials in Project @@ -196,31 +167,6 @@ foreach (string guid in materialGuids) return $"Found {unusedMaterials.Count} materials not referenced by prefabs. Verify scene and other asset references manually before deleting."; ``` -## Find Empty GameObjects - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List emptyObjects = new List(); - -foreach (GameObject obj in allObjects) -{ - Component[] components = obj.GetComponents(); - if (components.Length == 1 && obj.transform.childCount == 0) - { - emptyObjects.Add(obj.name); - } -} - -if (emptyObjects.Count == 0) -{ - return "No empty GameObjects found"; -} - -return $"Empty objects ({emptyObjects.Count}): {string.Join(", ", emptyObjects.Take(20))}"; -``` - ## Find Duplicate Names in Hierarchy ```csharp @@ -311,17 +257,13 @@ if (negativeScale.Count == 0) return $"Negative scale objects: {string.Join(", ", negativeScale.Take(10))}"; ``` -## Remove Empty Leaf GameObjects +## Find Empty Leaf GameObjects ```csharp using UnityEditor; GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Remove Empty Leaves"); - -int removedCount = 0; +List emptyObjects = new List(); foreach (GameObject obj in allObjects) { if (obj == null) continue; @@ -329,13 +271,16 @@ foreach (GameObject obj in allObjects) Component[] components = obj.GetComponents(); if (components.Length == 1 && obj.transform.childCount == 0) { - Undo.DestroyObjectImmediate(obj); - removedCount++; + emptyObjects.Add(obj.name); } } -Undo.CollapseUndoOperations(undoGroup); -return $"Removed {removedCount} empty leaf GameObjects"; +if (emptyObjects.Count == 0) +{ + return "No empty leaf GameObjects found"; +} + +return $"Empty leaf GameObjects ({emptyObjects.Count}): {string.Join(", ", emptyObjects.Take(20))}"; ``` ## Find Large Meshes diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md index f7bda52218..f33e3e68c9 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md @@ -18,57 +18,34 @@ return $"Set InputField text to: {input.text}"; ## Set Slider Value -```csharp -using UnityEngine.UI; - -Slider slider = GameObject.Find("Canvas/VolumeSlider")?.GetComponent(); -if (slider == null) return "Slider not found"; - -float oldValue = slider.value; -slider.value = 0.75f; -return $"Slider changed from {oldValue} to {slider.value}"; -``` - -## Set Slider Value with Bounds Check - ```csharp using UnityEngine.UI; using System.Linq; Slider[] sliders = Object.FindObjectsByType(FindObjectsSortMode.None); -Slider target = sliders.FirstOrDefault(s => s.gameObject.name == "HealthBar"); -if (target == null) return $"HealthBar not found. Available: {string.Join(", ", sliders.Select(s => s.gameObject.name))}"; +Slider target = sliders.FirstOrDefault(s => s.gameObject.name == "VolumeSlider"); +if (target == null) return $"VolumeSlider not found. Available: {string.Join(", ", sliders.Select(s => s.gameObject.name))}"; -float targetValue = 0.5f; +float targetValue = 0.75f; float clamped = Mathf.Clamp(targetValue, target.minValue, target.maxValue); target.value = clamped; return $"Set {target.gameObject.name} to {clamped} (range: {target.minValue}-{target.maxValue})"; ``` -## Toggle On/Off - -```csharp -using UnityEngine.UI; - -Toggle toggle = GameObject.Find("Canvas/MuteToggle")?.GetComponent(); -if (toggle == null) return "Toggle not found"; - -bool oldValue = toggle.isOn; -toggle.isOn = !toggle.isOn; -return $"Toggle changed from {oldValue} to {toggle.isOn}"; -``` +## Set Toggle Value -## Set Toggle to Specific Value +Assign a specific value for deterministic setup. Use `!toggle.isOn` only when the task explicitly asks to flip the current value. ```csharp using UnityEngine.UI; using System.Linq; Toggle[] toggles = Object.FindObjectsByType(FindObjectsSortMode.None); -Toggle target = toggles.FirstOrDefault(t => t.gameObject.name == "SoundToggle"); -if (target == null) return $"SoundToggle not found. Available: {string.Join(", ", toggles.Select(t => t.gameObject.name))}"; +Toggle target = toggles.FirstOrDefault(t => t.gameObject.name == "MuteToggle"); +if (target == null) return $"MuteToggle not found. Available: {string.Join(", ", toggles.Select(t => t.gameObject.name))}"; -target.isOn = true; +bool targetValue = true; +target.isOn = targetValue; return $"Set {target.gameObject.name} to {target.isOn}"; ``` diff --git a/.agents/skills/uloop-execute-dynamic-code/references/scene-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/scene-operations.md index d34db4c49f..d5e17890d4 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/scene-operations.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/scene-operations.md @@ -64,13 +64,6 @@ if (obj == null) return $"Found: {obj.name} at {obj.transform.position}"; ``` -## Find GameObjects by Tag - -```csharp -GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); -return $"Found {enemies.Length} GameObjects with tag 'Enemy'"; -``` - ## Set Parent ```csharp diff --git a/.agents/skills/uloop-execute-dynamic-code/references/scriptableobject.md b/.agents/skills/uloop-execute-dynamic-code/references/scriptableobject.md index debc01b9f8..1827a4fb5a 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/scriptableobject.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/scriptableobject.md @@ -33,32 +33,7 @@ return $"Created {so.GetType().Name} at {path}"; ## Modify ScriptableObject with SerializedObject -```csharp -using UnityEditor; - -string path = "Assets/Data/MyData.asset"; -ScriptableObject so = AssetDatabase.LoadAssetAtPath(path); - -if (so == null) -{ - return $"Asset not found at {path}"; -} - -SerializedObject serializedObj = new SerializedObject(so); -SerializedProperty prop = serializedObj.FindProperty("myField"); - -if (prop != null) -{ - prop.stringValue = "New Value"; - serializedObj.ApplyModifiedProperties(); - EditorUtility.SetDirty(so); - AssetDatabase.SaveAssets(); - return "Property updated"; -} -return "Property 'myField' not found"; -``` - -## Set Int/Float/Bool Properties +Use `FindProperty` with the matching typed setter (`stringValue`, `intValue`, `floatValue`, `boolValue`, etc.). ```csharp using UnityEditor; diff --git a/.agents/skills/uloop-execute-dynamic-code/references/selection-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/selection-operations.md index 629c57d8b2..c23f5587f3 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/selection-operations.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/selection-operations.md @@ -65,12 +65,15 @@ Selection.objects = enemies; return $"Selected {enemies.Length} enemies"; ``` -## Get Top-Level Transforms Only +## Get Transforms by Selection Mode + +`SelectionMode.TopLevel` excludes children, `Deep` includes children, `Editable` excludes locked/immutable objects. ```csharp using UnityEditor; using System.Collections.Generic; +// TopLevel | Deep | Editable Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel); if (transforms.Length == 0) { @@ -82,45 +85,13 @@ foreach (Transform t in transforms) { names.Add(t.name); } -return $"Top-level: {string.Join(", ", names)}"; -``` - -## Get Deep Selection (Including Children) - -```csharp -using UnityEditor; - -Transform[] transforms = Selection.GetTransforms(SelectionMode.Deep); -if (transforms.Length == 0) -{ - return "No transforms selected"; -} - -return $"Deep selection count: {transforms.Length}"; -``` - -## Get Editable Objects Only - -```csharp -using UnityEditor; -using System.Collections.Generic; - -Transform[] transforms = Selection.GetTransforms(SelectionMode.Editable); -if (transforms.Length == 0) -{ - return "No editable transforms selected"; -} - -List names = new List(); -foreach (Transform t in transforms) -{ - names.Add(t.name); -} -return $"Editable: {string.Join(", ", names)}"; +return $"Selected ({transforms.Length}): {string.Join(", ", names)}"; ``` ## Get Selected Assets +Use `Selection.GetFiltered` when you need the object reference, or `Selection.assetGUIDs` when you need GUIDs. + ```csharp using UnityEditor; using System.Collections.Generic; @@ -139,26 +110,6 @@ foreach (Object asset in selectedAssets) return $"Assets: {string.Join(", ", paths)}"; ``` -## Get Selected Asset GUIDs - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string[] guids = Selection.assetGUIDs; -if (guids.Length == 0) -{ - return "No assets selected"; -} - -List paths = new List(); -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Selected assets: {string.Join(", ", paths)}"; -``` - ## Select All Children of Selected Object ```csharp diff --git a/.agents/skills/uloop-find-game-objects/SKILL.md b/.agents/skills/uloop-find-game-objects/SKILL.md index 620c44e32c..24edd909a3 100644 --- a/.agents/skills/uloop-find-game-objects/SKILL.md +++ b/.agents/skills/uloop-find-game-objects/SKILL.md @@ -77,7 +77,7 @@ Returns JSON with: - `tag` (string): GameObject tag - `layer` (number): Layer index - `components` (array): Each entry has `type` (short name, e.g., `Rigidbody`), `fullTypeName` (e.g., `UnityEngine.Rigidbody`), and `properties` (array of Inspector-visible `{name, type, value}` pairs) -- `totalFound` (number): Number of results returned inline, or number exported for multi-selection file output. For search modes, this is after `--max-results` clipping and serialization. +- `totalFound` (number): Results returned (after `--max-results` clipping). For multi-selection file export, this is the number exported. - `errorMessage` (string): Top-level failure summary (empty on success) - `processingErrors` (array): Selected-mode per-GameObject serialization failures, each `{gameObjectName, gameObjectPath, error}`. Omitted/null or empty on clean runs. diff --git a/.agents/skills/uloop-focus-window/SKILL.md b/.agents/skills/uloop-focus-window/SKILL.md index 515bfa29c3..e2ca7a23bb 100644 --- a/.agents/skills/uloop-focus-window/SKILL.md +++ b/.agents/skills/uloop-focus-window/SKILL.md @@ -13,10 +13,6 @@ Bring Unity Editor window to front using OS-level commands. uloop focus-window ``` -## Parameters - -None. - ## Global Options | Option | Description | @@ -36,11 +32,7 @@ Returns JSON with: - `success`: Whether the focus operation succeeded - `message`: Status message (e.g. `Unity Editor window focused (PID: 12345)`, or the failure reason such as `Unity project not found` / `No running Unity process found for this project` / `Failed to focus Unity window: `) -These are the only two fields. There is no PID, window-handle, or platform field returned to the caller. - ## Notes - **Works even when Unity is busy** (compiling, domain reload, etc.) -- Uses OS-level commands (osascript on macOS, PowerShell on Windows) - Useful before `uloop capture-unity-window` to ensure the target window is visible -- Brings the main Unity Editor window to the foreground diff --git a/.agents/skills/uloop-get-logs/SKILL.md b/.agents/skills/uloop-get-logs/SKILL.md index 2ecb25aff7..57d1d97f62 100644 --- a/.agents/skills/uloop-get-logs/SKILL.md +++ b/.agents/skills/uloop-get-logs/SKILL.md @@ -8,8 +8,6 @@ description: "Read current Unity Console entries from a running Editor. Use duri Retrieve logs from Unity Console. -When a pause-point marker pauses Unity and the value you need is a method local, intermediate calculation, or branch reason, read the focused `Debug.Log` entry added immediately before the marker before resuming PlayMode. Use `--search-text ` so the marker and its log are checked as one breakpoint-style proof. - ## Usage ```bash @@ -54,10 +52,7 @@ uloop get-logs --search-text "Missing.*Component" --use-regex Returns JSON with: - `totalCount` (number): Total logs available before max-count clipping - `displayedCount` (number): Logs returned in this response (≤ `--max-count`) -- `logType` (string): The `--log-type` filter that was applied -- `maxCount` (number): The `--max-count` cap that was applied -- `searchText` (string): The `--search-text` filter that was applied (empty when omitted) -- `includeStackTrace` (boolean): Whether stack traces are included in `logs[]` +- Input filters (`logType`, `maxCount`, `searchText`, `includeStackTrace`) are echoed back in the response - `logs` (array): Each entry has: - `type` (string): `"Error"`, `"Warning"`, or `"Log"` - `message` (string): Log message body diff --git a/.agents/skills/uloop-launch/SKILL.md b/.agents/skills/uloop-launch/SKILL.md index 2bf0e0929b..7bbee3d3e5 100644 --- a/.agents/skills/uloop-launch/SKILL.md +++ b/.agents/skills/uloop-launch/SKILL.md @@ -47,22 +47,24 @@ uloop launch --quit ## Output -- Prints detected Unity version -- Prints project path +May print status/progress lines before the final JSON payload, such as project path, detected Unity version, or readiness wait messages. + +The final JSON payload includes: +- `success`: whether the command completed +- `ready`: whether Unity CLI Loop is ready for commands +- `serverReady`: whether the Unity CLI Loop server accepted requests +- `projectIpcReady`: whether the project IPC path accepted tool requests +- `alreadyRunning`: whether an existing Unity process was reused +- `launched`: whether this command launched a Unity process +- `restarted`: whether this command stopped an existing process and launched a new one +- `quit`: whether this command stopped Unity without launching a new process +- `previousProcessId`: process ID stopped by restart or quit, when available +- `currentProcessId`: current Unity process ID, when available +- `projectRoot`: resolved project root +- `message`: readiness summary + +## Notes + - If Unity is already running, focuses the existing window and verifies tool readiness -- If the process scan is blocked by the environment (e.g. sandboxed `ps`), plain launch falls back to probing the project IPC; when Unity responds it reports `alreadyRunning: true` without focusing the window instead of failing. `--restart` and `--quit` still fail because they need the process id -- If launching or restarting, prints when it is waiting for Unity CLI Loop server readiness -- If launching or restarting, waits until Unity finishes startup and the CLI can connect to the project -- Successful launch, restart, existing-process, and quit paths return JSON with: - - `success`: whether the command completed - - `ready`: whether Unity CLI Loop is ready for commands - - `serverReady`: whether the Unity CLI Loop server accepted requests - - `projectIpcReady`: whether the project IPC path accepted tool requests - - `alreadyRunning`: whether an existing Unity process was reused - - `launched`: whether this command launched a Unity process - - `restarted`: whether this command stopped an existing process and launched a new one - - `quit`: whether this command stopped Unity without launching a new process - - `previousProcessId`: process ID stopped by restart or quit, when available - - `currentProcessId`: current Unity process ID, when available - - `projectRoot`: resolved project root - - `message`: readiness summary +- If process scan is blocked (e.g. sandboxed `ps`), plain launch falls back to IPC probing; `--restart` and `--quit` still fail because they need the process id +- The command waits until Unity finishes startup and the CLI can connect before returning diff --git a/.agents/skills/uloop-record-input/SKILL.md b/.agents/skills/uloop-record-input/SKILL.md index 6b1c530f38..a76d38cb33 100644 --- a/.agents/skills/uloop-record-input/SKILL.md +++ b/.agents/skills/uloop-record-input/SKILL.md @@ -39,8 +39,7 @@ Replay injects input frame-by-frame, so the game must also be deterministic to p ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) -- Use this only when the project already uses the New Input System. +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. ## Output @@ -52,4 +51,4 @@ The CLI prints JSON with: - `totalFrames`: Number of frames recorded (nullable int; populated on `Stop` only) - `durationSeconds`: Recording duration in seconds (nullable float; populated on `Stop` only) -The CLI output contains only these six payload fields. Internal metadata such as `ver` is removed before printing. There is no `recordingId`, `startTimestamp`, `keysCaptured`, or per-frame data in the response — frame data lives only in the JSON file at `outputPath`. +Frame data lives only in the JSON file at `outputPath`; internal metadata fields are not included in the response. diff --git a/.agents/skills/uloop-replay-input/SKILL.md b/.agents/skills/uloop-replay-input/SKILL.md index d69e1ad07e..81793dbc8e 100644 --- a/.agents/skills/uloop-replay-input/SKILL.md +++ b/.agents/skills/uloop-replay-input/SKILL.md @@ -43,8 +43,7 @@ Replay injects the exact same input frame-by-frame, but the game must also be de ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) -- Use this only when the project already uses the New Input System. +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. ## Output @@ -57,5 +56,3 @@ Returns JSON with: - `totalFrames`: Total frames in the recording (nullable int) - `progress`: Replay progress (nullable float in 0.0 – 1.0) - `isReplaying`: Whether replay is currently active (nullable bool) - -These are the only eight fields. There is no `loopCount`, `elapsedSeconds`, `overlayVisible`, or per-frame inspection data in the response. diff --git a/.agents/skills/uloop-run-tests/SKILL.md b/.agents/skills/uloop-run-tests/SKILL.md index ba6909c219..7660586e11 100644 --- a/.agents/skills/uloop-run-tests/SKILL.md +++ b/.agents/skills/uloop-run-tests/SKILL.md @@ -8,11 +8,11 @@ description: "Run Unity Test Runner and report detailed results. Use for EditMod Execute Unity Test Runner. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `xmlPath` for detailed failure diagnosis. -Before running `uloop run-tests`, run `uloop compile` for the same Unity project when the current task created, deleted, renamed, moved, or edited C# source files, test files, `.asmdef`, `.asmref`, package manifest files, or scripting define settings. This refreshes the AssetDatabase, lets Unity discover new tests, and surfaces compile errors before test execution. You may skip this compile step when rerunning tests without code or assembly-definition changes since the last successful compile. +Before running tests, run `uloop compile` first if you created, deleted, renamed, moved, or edited C# files, test files, `.asmdef`/`.asmref`, or package manifests since the last successful compile. This refreshes the AssetDatabase and surfaces compile errors before test execution. Before executing tests, `uloop run-tests` saves unsaved loaded Scene changes and unsaved current Prefab Stage changes by default. If saving fails, it returns `success: false`, keeps `testCount` at `0`, lists the unsaved items in `message`, and does not start the Unity Test Runner. -When a run returns `status: "NoTestsFound"` or `noTestsFound: true`, no matching tests were discovered. This is not the same as a failed test case: `hasFailures` remains `false`, `failedCount` remains `0`, and `noTestsFoundExplanation` explains the zero-discovery state for agents. When an unfiltered `--filter-type all` run also has `message` set to the no-tests message, read the `message` for asmdef hints. `run-tests` only appends those hints when it detects likely test assembly configuration issues; exact, regex, and assembly filter misses keep the plain no-tests message. +`NoTestsFound` means zero tests matched — not a test failure. Check `noTestsFoundExplanation` and `message` for asmdef hints. ## Usage diff --git a/.agents/skills/uloop-screenshot/SKILL.md b/.agents/skills/uloop-screenshot/SKILL.md index d10d199ea2..bc721a5ee7 100644 --- a/.agents/skills/uloop-screenshot/SKILL.md +++ b/.agents/skills/uloop-screenshot/SKILL.md @@ -67,9 +67,6 @@ uloop screenshot --window-name Project --match-mode prefix # Save screenshot to a specific directory uloop screenshot --output-directory /tmp/screenshots - -# Combine options -uloop screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots ``` ## Output diff --git a/.agents/skills/uloop-simulate-keyboard/SKILL.md b/.agents/skills/uloop-simulate-keyboard/SKILL.md index 6cb374cc93..7b65d6b135 100644 --- a/.agents/skills/uloop-simulate-keyboard/SKILL.md +++ b/.agents/skills/uloop-simulate-keyboard/SKILL.md @@ -42,16 +42,11 @@ uloop simulate-keyboard --action --key [options] Use `Press` for edge-triggered keyboard code such as `Keyboard.current.spaceKey.wasPressedThisFrame`. `KeyDown` emits one initial press edge, then only keeps the key held. It does not keep `wasPressedThisFrame` true while the key remains held. If a successful `Press` or `KeyDown` leaves `Keyboard.current..isPressed` true but runtime state does not change, do not immediately rewrite the user's runtime code to `isPressed`. First verify that the target component is active during the command, that it polls input in the configured Input System update phase, and that a missed `KeyDown` edge is followed by `KeyUp` before retrying. -Use `KeyDown` / `KeyUp` when the scenario intentionally needs a held key. - ### Pause Point Inspection (Standard for E2E) -- Use `UloopPausePoint.Pause("")` with `uloop-wait-for-pause-point` as the standard frame proof when this input drives a state transition you are verifying. Final logs, screenshots, and durable state supplement the paused-frame check but do not replace it. -- Put the marker at a natural state transition after the app consumed the key, such as after a command is accepted, a state mutation is committed, an evaluation step resolves, or a dependent component is updated. Do not place it immediately after sending `simulate-keyboard`. -- If the key handler has local variables, intermediate calculations, or branch reasons that `execute-dynamic-code` cannot inspect after the fact, log just those values near `UloopPausePoint.Pause("")` and read them with `uloop-get-logs` while Unity is paused. A pause point hit proves the line was reached, not the frame-local values. -- Treat `simulate-keyboard success=true`, generic action logs, and final durable counters as useful evidence, but not as paused-frame proof. -- If the response has `interruptedByPausePoint: true`, Unity is paused for inspection and the tool released its held input bookkeeping. If a `UloopPausePoint.Pause` marker caused the pause, `pausePointId` and `pausePointHitCount` identify it. Use `uloop get-logs`, `uloop get-hierarchy`, `uloop find-game-objects`, or `uloop execute-dynamic-code` before resuming. -- Use distinct marker ids for strict phases, for example `input-read`, `state-updated`, and `result-committed`. +For standard frame proof when this input drives a state transition, follow the `uloop-wait-for-pause-point` skill. Place markers after the app consumed the key, not immediately after `simulate-keyboard`. + +- If `interruptedByPausePoint: true`, Unity is paused and input bookkeeping was released. `pausePointId` and `pausePointHitCount` identify the marker. `pressEdgeObserved` is still reported on pause-point interruptions. ### KeyDown/KeyUp Rules @@ -103,6 +98,5 @@ Returns JSON with: ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) -- Use this only when the project already uses the New Input System. +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. - Game code must read input via Input System API (e.g. `Keyboard.current[Key.W].isPressed`), not legacy `Input.GetKey()` diff --git a/.agents/skills/uloop-simulate-mouse-input/SKILL.md b/.agents/skills/uloop-simulate-mouse-input/SKILL.md index 12b73569e4..8bc5b09e69 100644 --- a/.agents/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-input/SKILL.md @@ -50,11 +50,9 @@ uloop simulate-mouse-input --action [options] ### Pause Point Inspection (Standard for E2E) -- Use `UloopPausePoint.Pause("")` with `uloop-wait-for-pause-point` as the standard frame proof when this input drives a state transition you are verifying. Final logs, screenshots, and durable state supplement the paused-frame check but do not replace it. -- Place the pause point at a natural state transition after the app consumed the mouse input, such as after a command is accepted, a state mutation is committed, an evaluation step resolves, a tracked value changes, or a dependent component is updated. Do not place it immediately after sending `simulate-mouse-input`. -- If the mouse handler has local variables, intermediate calculations, or branch reasons that `execute-dynamic-code` cannot inspect after the fact, log just those values near `UloopPausePoint.Pause("")` and read them with `uloop-get-logs` while Unity is paused. A pause point hit proves the line was reached, not the frame-local values. -- If the response has `interruptedByPausePoint: true`, Unity is paused for inspection and the tool released its held input bookkeeping. `pausePointId` and `pausePointHitCount` identify the pause point that paused Unity. Use `uloop get-logs`, `uloop get-hierarchy`, `uloop find-game-objects`, or `uloop execute-dynamic-code` before resuming. -- Use distinct ids for strict phases, for example `input-read`, `state-updated`, and `result-committed`. +For standard frame proof when this input drives a state transition, follow the `uloop-wait-for-pause-point` skill. Place markers after the app consumed the mouse input, not immediately after `simulate-mouse-input`. + +- If `interruptedByPausePoint: true`, Unity is paused and input bookkeeping was released. `pausePointId` and `pausePointHitCount` identify the marker. - Remove temporary pause-point/log instrumentation before final validation when it was added only for inspection. ### Global Options (optional) @@ -66,14 +64,16 @@ uloop simulate-mouse-input --action [options] ## When to use this vs simulate-mouse-ui +All rows below assume the New Input System is installed. + | Scenario | Tool | |----------|------| | Click a Unity UI Button (IPointerClickHandler) | `simulate-mouse-ui` | -| Runtime logic reads `Mouse.current.leftButton` | `simulate-mouse-input` when the project uses the New Input System | -| Runtime logic reads right-click | `simulate-mouse-input --button Right` when the project uses the New Input System | +| Runtime logic reads `Mouse.current.leftButton` | `simulate-mouse-input` | +| Runtime logic reads right-click | `simulate-mouse-input --button Right` | | Drag a UI slider | `simulate-mouse-ui --action Drag` | -| Runtime logic reads `Mouse.current.delta` | `simulate-mouse-input --action MoveDelta` when the project uses the New Input System | -| Runtime logic reads `Mouse.current.scroll` | `simulate-mouse-input --action Scroll` when the project uses the New Input System | +| Runtime logic reads `Mouse.current.delta` | `simulate-mouse-input --action MoveDelta` | +| Runtime logic reads `Mouse.current.scroll` | `simulate-mouse-input --action Scroll` | ## Examples @@ -103,9 +103,8 @@ uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --dura ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. - Game code must read input via Input System API (e.g. `Mouse.current.leftButton.wasPressedThisFrame`) -- Use this only when the project already uses the New Input System. ## Output @@ -121,4 +120,4 @@ Returns JSON with: - `pausePointHitCount`: The hit count for that `UloopPausePoint.Pause("")` - `pausePointHits` (array, nullable): Every marker hit during this input as `{id, hitCount}` entries, in hit order. Read this when one input may trigger several markers; `pausePointId` only names the latest one -There is no `deltaX`, `deltaY`, `scrollX`, `scrollY`, `duration`, or hit-element field in the response — only the issued action, button, target position, and Pause Point interruption state are echoed back. Verify visual outcome with a follow-up screenshot. +Verify visual outcome with a follow-up screenshot. diff --git a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md index f5e2799aec..8a9a797b6b 100644 --- a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md @@ -57,7 +57,6 @@ uloop simulate-mouse-ui --action --x --y [options] - `DragStart` must be called before `DragMove` or `DragEnd` - `DragEnd` must be called to release an active drag — failing to call it leaves drag state stuck - Calling `DragMove` or `DragEnd` without an active drag returns an error -- `Drag`, `DragStart`, `DragMove`, and `DragEnd` only support `--button Left` ### Global Options (all optional, mutually exclusive) @@ -78,11 +77,8 @@ uloop simulate-mouse-ui --action --x --y [options] ## Pause Point Inspection (Standard for E2E) -- Use `UloopPausePoint.Pause("")` with `uloop wait-for-pause-point` as the standard frame proof when this input drives a state transition you are verifying. Final logs, screenshots, and durable state supplement the paused-frame check but do not replace it. -- Place the pause point at a natural transition after the app consumed the UI event, such as after a command is accepted, a state mutation is committed, a tracked value changes, a UI/domain state syncs, or a success/failure/end condition is entered. -- If the UI handler has local variables, intermediate calculations, or branch reasons that `uloop execute-dynamic-code` cannot inspect after the fact, log just those values near `UloopPausePoint.Pause("")` and read them with `uloop get-logs` while Unity is paused. A pause point hit proves the line was reached, not the frame-local values. -- Treat `uloop simulate-mouse-ui` `success=true`, generic action logs, and final durable counters as useful evidence, not paused-frame proof. -- If a `UloopPausePoint.Pause` pauses Unity, inspect with `uloop get-logs`, `uloop get-hierarchy`, `uloop find-game-objects`, or `uloop execute-dynamic-code` before resuming. +For standard frame proof when this UI input drives a state transition, follow the `uloop-wait-for-pause-point` skill. Place markers after the app consumed the UI event, not immediately after `simulate-mouse-ui`. + - Remove temporary pause-point/log instrumentation before final validation when it was added only for inspection. ## Examples @@ -138,6 +134,6 @@ Returns JSON with: - `endPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `endPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `button`, `duration`, `dragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +Verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. Note: Click and LongPress on empty space (no UI element) still return `success = true` with `hitGameObjectName = null`. Drag actions on empty space return `success = false`. diff --git a/.agents/skills/uloop-wait-for-pause-point/SKILL.md b/.agents/skills/uloop-wait-for-pause-point/SKILL.md index 2ac9b2c58b..14699e0deb 100644 --- a/.agents/skills/uloop-wait-for-pause-point/SKILL.md +++ b/.agents/skills/uloop-wait-for-pause-point/SKILL.md @@ -47,7 +47,6 @@ Use `generation`, `enabledAtUtc`, and the hit sequence fields from the hit or st - Consider a pause point during E2E passes when transition-frame evidence would add confidence, even if durable state, logs, or screenshots can later confirm the final result. - Use this before reaching for `Time.timeScale`, sleeps, repeated polling, or after-the-fact `execute-dynamic-code`; those checks can supplement the paused-frame proof, but they are not substitutes. - If the value you need is a method local, an intermediate calculation, or a branch reason that `execute-dynamic-code` cannot reach, add a focused `Debug.Log` immediately before the marker and read it with `get-logs` while paused. Do not count the breakpoint check complete until the matching log has been read. -- Good pause points include after input is consumed, a command is accepted, a state mutation is committed, an evaluation step resolves, a tracked value changes, a UI/domain state syncs, or a success/failure/end condition is entered. - Treat the pause like a lightweight breakpoint for one important transition: combine nearby debug logs with paused-frame inspection to confirm the variables and component state at that point. - Do not treat `simulate-* success=true`, generic action logs, sleeps/retries, testing-only counters, or `Time.timeScale` changes as paused-frame proof. - Skip this only for ordinary persistent-state checks when you are not validating simulated input delivery, event ordering, or transition-frame fidelity. diff --git a/.claude/commands/link-go-cli.md b/.claude/commands/link-go-cli.md deleted file mode 100644 index e901d79208..0000000000 --- a/.claude/commands/link-go-cli.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -description: Link this repository's native Go uloop CLI as the global uloop command -allowed-tools: Bash, Read, Grep ---- - -# Link Go CLI - -Link this repository's in-development Go CLI as the global `uloop` command. - -## Goal - -Build and verify the native Go CLI under `cli`, then point the `uloop` command on PATH at this checkout's local development binary. - -## Important Assumptions - -- The target is `cli`. Do not use older package-local CLI paths or `npm link` workflows. -- The development binary is generated under `cli/dist/.../uloop`. It is ignored by git and must not be committed. -- Use `scripts/use-local-uloop.sh link|status|restore` instead of hand-writing the symlink. -- Do not treat the link as complete without verification. - -## Steps - -### Step 1: Confirm the Repository Root - -```bash -git rev-parse --show-toplevel -git status --short --branch -``` - -Confirm that `cli/go.mod`, `scripts/check-go-cli.sh`, and `scripts/use-local-uloop.sh` exist. - -### Step 2: Build and Verify the Go CLI - -```bash -scripts/check-go-cli.sh -``` - -This script checks Go CLI formatting, vet, lint, tests, and local dist validation. If it fails because local development binaries are missing, run `scripts/build-go-cli.sh`, then rerun the check. - -### Step 3: Link the Local CLI - -```bash -scripts/use-local-uloop.sh link -``` - -The script selects `cli/dist/darwin-arm64/uloop` or `cli/dist/darwin-amd64/uloop` on macOS, backs up an existing global `uloop` as `uloop.before-local-link`, and creates the symlink. - -Use `scripts/use-local-uloop.sh status` to inspect the current state and `scripts/use-local-uloop.sh restore` to put the previous global command back. - -### Step 4: Verify the Link Result - -```bash -which uloop -readlink "$(which uloop)" -uloop --version -uloop --help -``` - -Confirm that the `readlink` result points at this checkout's `cli/dist/.../uloop`. - -## Completion Report - -Briefly report the following: - -- Result of `which uloop` -- Symlink target -- `uloop --version` -- `scripts/check-go-cli.sh` result -- Whether a backup exists diff --git a/.claude/rules/cli.md b/.claude/rules/cli.md deleted file mode 100644 index 9ad43f6d5a..0000000000 --- a/.claude/rules/cli.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -paths: cli/** ---- - -# uloop CLI - -The uloop CLI is the native Go command surface for communicating with Unity Editor. - -## Architecture - -- `cmd/uloop` contains the global/user-facing `uloop` entrypoint. -- `internal/cli` contains command parsing, command execution, skills, readiness handling, and output formatting. -- `internal/unityipc` contains Unity Editor IPC transport and framing. -- `internal/install`, `internal/uninstall`, and `internal/update` contain native installer behavior. -- `internal/project`, `internal/tools`, `internal/skills`, and `internal/version` contain shared helpers. -- `layout-contract.json` and `contract.json` define the versioned CLI layout contract. -- `cli/dist` contains generated local development binaries and release assets. It is ignored by git. - -## Directory Structure - -```text -cli/ -├── cmd/uloop/ # Native CLI entrypoint -├── internal/cli/ # Command surface and output -├── internal/unityipc/ # Unity IPC client and framing -├── internal/install/ # Native install command helpers -├── internal/uninstall/ # Native uninstall command helpers -├── internal/update/ # Native update command helpers -├── internal/automation/ # Release and workflow automation helpers -├── internal/project/ # Unity project path helpers -├── internal/skills/ # Bundled skill source helpers -├── internal/tools/ # Tool catalog contracts -├── internal/version/ # Version comparison helpers -├── dist/ # Ignored generated binaries and release assets -├── contract.json -└── layout-contract.json -``` - -## Build and Validation - -Use the repository scripts: - -```bash -scripts/check-go-cli.sh -``` - -This runs the Go CLI source checks and validates generated local dist artifacts. - -Release asset packaging is handled by: - -```bash -scripts/package-go-cli.sh -scripts/verify-native-cli-release-assets.sh -``` - -## Native CLI Releases - -The v3 CLI is released through GitHub Release assets built by `native-cli-publish.yml`. - -Do not add npm publish or npm version-check workflows for the v3 CLI. - -Expected release assets: - -- `install.sh` -- `install.ps1` -- `uloop-darwin-amd64.tar.gz` -- `uloop-darwin-arm64.tar.gz` -- `uloop-windows-amd64.zip` -- matching `.sha256` files for each binary archive - -## Skills System - -Skills are collected from two sources: - -1. CLI-only bundled skills under `Packages/src/Editor/CliOnlyTools~/` -2. Project skills scanned from Unity project's `Editor/` folders: - - `Assets/**/Editor/` - - `Packages/**/Editor/` - - `Library/PackageCache/**/Editor/` - -Skills with `internal: true` in frontmatter are excluded from the user-facing bundled skills list. - -Currently internal skills: - -- `uloop-get-project-info` -- `uloop-get-version` - -When updating README documentation about bundled skills count, remember to exclude internal skills from the count. - -## Domain Reload and Connection Drops - -After `compile` command execution, Unity triggers a Domain Reload that disconnects the Unity-side server for a few seconds. This behavior is unavoidable. - -When writing CLI tests: - -- Prefer pure Go tests for command, contract, and dispatch behavior. -- Use retry-aware helpers for commands that run after `compile`. -- Place compile-related E2E checks at the end of a suite when Unity Editor state is involved. diff --git a/.claude/skills/find-orphaned-meta/SKILL.md b/.claude/skills/find-orphaned-meta/SKILL.md deleted file mode 100644 index b8f5ce1d3a..0000000000 --- a/.claude/skills/find-orphaned-meta/SKILL.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: find-orphaned-meta -description: "Find and clean up orphaned .meta files in a Unity project. Use when you need to: (1) Find .meta files whose corresponding file or folder no longer exists, (2) Clean up Unity warnings about missing assets caused by stale .meta files, (3) Audit .meta hygiene after git operations like branch switches, merges, or file deletions. Also use proactively after deleting files or folders in a Unity project." ---- - -# Task - -Find orphaned .meta files in the Unity project: $ARGUMENTS - -## What - -Detect `.meta` files in `Assets/` and `Packages/` whose corresponding file or folder no longer exists. Unity generates a `.meta` file for every asset — when the asset is deleted but the `.meta` remains, Unity logs warnings and the stale `.meta` pollutes the repository. - -## When - -Use when you need to: -1. Find `.meta` files left behind after file/folder deletions -2. Diagnose Unity warnings like "A meta data file (.meta) exists but its folder/asset can't be found" -3. Clean up after git operations (branch switch, merge, rebase) that may leave orphaned `.meta` files - -## How - -### Step 1: Run the detection script - -```bash -.claude/skills/find-orphaned-meta/scripts/find-orphaned-meta.sh -``` - -The script scans `Assets/` and `Packages/` by default. Pass directory arguments to scan specific paths. - -Output: one orphaned `.meta` path per line. Empty output means no orphans found. - -### Step 2: Report findings - -- If no orphans: report "No orphaned .meta files found." -- If orphans found: list them and ask the user whether to delete them. - -### Step 3: Delete (with user confirmation) - -After user approves, delete the orphaned `.meta` files: - -```bash -rm -- "" -``` - -Then run `uloop compile` to verify Unity no longer reports warnings about missing assets. diff --git a/.claude/skills/find-orphaned-meta/scripts/find-orphaned-meta.sh b/.claude/skills/find-orphaned-meta/scripts/find-orphaned-meta.sh deleted file mode 100755 index 8d1a2335ee..0000000000 --- a/.claude/skills/find-orphaned-meta/scripts/find-orphaned-meta.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -# Find orphaned .meta files in a Unity project. -# An orphaned .meta is one whose corresponding file or folder no longer exists. -# -# Usage: find-orphaned-meta.sh [directory ...] -# Defaults to "Assets Packages" if no arguments given. -# -# Output: one orphaned .meta path per line (relative to cwd). -# Exit code: 0 if no orphans, 1 if orphans found. - -set -e - -if [ $# -eq 0 ]; then - set -- Assets Packages -fi - -tmpfile=$(mktemp) -trap 'rm -f "$tmpfile"' EXIT - -for dir in "$@"; do - [ -d "$dir" ] || continue - find "$dir" -name "*.meta" -type f | while IFS= read -r meta; do - target="${meta%.meta}" - if [ ! -e "$target" ]; then - echo "$meta" - echo "1" > "$tmpfile" - fi - done -done - -if [ -s "$tmpfile" ]; then - exit 1 -fi -exit 0 diff --git a/.claude/skills/simulate-mouse-demo/SKILL.md b/.claude/skills/simulate-mouse-demo/SKILL.md deleted file mode 100644 index 5bb28c3070..0000000000 --- a/.claude/skills/simulate-mouse-demo/SKILL.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -name: simulate-mouse-demo -description: "Run the SimulateMouse demo scenario on SimulateMouseDemoScene. Clicks buttons, long-presses, drags boxes, split-drags through waypoints, and operates the virtual pad. Use when the user asks to run the simulate-mouse demo, test mouse simulation, or exercise the demo scene." -context: fork ---- - -# Task - -Run the SimulateMouse demo scenario: $ARGUMENTS - -## What - -Automate the SimulateMouse demo scene by clicking buttons, one-shot dragging boxes, split-dragging a box through multiple waypoints to the drop zone, and operating the virtual pad with DragStart/DragMove/DragEnd. Exercises Click, Drag, DragStart, DragMove, and DragEnd across all interactive elements. - -## When - -Use when you need to: -1. Run the simulate-mouse demo to verify click, drag, split-drag, and virtual pad functionality -2. Test mouse simulation on the demo scene after code changes -3. Exercise the demo scene end-to-end (buttons + one-shot drag + split drag + virtual pad) - -## How - -### Prerequisites - -- Unity must be running with **SimulateMouseDemoScene** loaded -- **PlayMode** must be active - -If PlayMode is not active, start it with `uloop control-play-mode --action Play` and wait a moment for the scene to initialize. - -### Step 1: Discover UI element coordinates - -Take an annotated screenshot to get exact coordinates for each interactive element: - -```bash -uloop screenshot --capture-mode rendering --annotate-elements -``` - -From the `annotatedElements` array in the response, extract `simX` and `simY` for: -- **ClickButton1** — red button -- **ClickButton2** — blue button -- **DropZone** — the drag target area -- **RedBox** — red draggable box -- **GreenBox** — green draggable box -- **BlueBox** — blue draggable box -- **LongPressButton** — purple long-press button - -**VirtualPadBackground** is also listed in the `annotatedElements` output (`type`: "Draggable"). Extract its `simX` and `simY` directly — these are used as `` and `` in the command examples below. - -### Step 2: Click buttons, one-shot drag, and split drag — chain all in one Bash call - -**IMPORTANT**: Chain all commands with `&&` in a **single Bash tool call** to eliminate round-trip latency between operations. The simulate-mouse tool uses a single-pointer model, so commands must run sequentially (not in parallel), but chaining avoids AI round-trip delays. - -**Phase 1 — Button clicks**: Alternate ClickButton1 and ClickButton2 four times. - -**Phase 1.5 — LongPress**: LongPress on LongPressButton for 3 seconds. The button gradually changes color during the hold. On first activation it turns orange and shows "Activated!"; on second activation it toggles back to purple and shows "Hold 3s". - -**Phase 2 — One-shot Drag**: Drag RedBox to DropZone top area (Y offset -80 from center) so it doesn't overlap subsequent drops. - -**Phase 3 — Split drag with DragMove**: Use DragStart on GreenBox, then DragMove through 3 waypoints that zigzag across the screen, and finally DragEnd at the DropZone center. Use `--drag-speed 400` for DragMove/DragEnd so the movement is slow enough to observe the cursor overlay and trail line. - -Waypoint design (relative to the midpoint between GreenBox and DropZone): -- Waypoint 1: far right, slightly above — tests horizontal sweep -- Waypoint 2: far left, below — tests direction reversal -- Waypoint 3: above DropZone — tests vertical approach -- DragEnd: at DropZone center (Y offset 0) - -**Phase 4 — One-shot Drag**: Drag BlueBox to DropZone bottom area (Y offset +80 from center) so it doesn't overlap previous drops. - -**Phase 5 — Virtual Pad**: Use DragStart on VirtualPadBackground center, then DragMove through 8 random directions within padRadius (80px from center) to exercise the joystick, and DragEnd back at center. Use `--drag-speed 300` for visible movement. The direction arrow should rotate to follow each DragMove direction. - -Waypoint design (offsets from VirtualPadBackground center): -- Move 1: upper-right (+60, -60 in sim coords) -- Move 2: lower-left (-70, +50) -- Move 3: straight up (0, -75) -- Move 4: straight right (+80, 0) -- Move 5: lower-right (+50, +60) -- Move 6: straight left (-80, 0) -- Move 7: upper-left (-55, -65) -- Move 8: straight down (0, +75) -- DragEnd: back to center (0, 0) - -```bash -uloop simulate-mouse-ui --action Click --x --y && sleep 0.3 && \ -uloop simulate-mouse-ui --action Click --x --y && sleep 0.3 && \ -uloop simulate-mouse-ui --action Click --x --y && sleep 0.3 && \ -uloop simulate-mouse-ui --action Click --x --y && sleep 0.3 && \ -uloop simulate-mouse-ui --action LongPress --x --y --duration 3.0 && sleep 0.3 && \ -uloop simulate-mouse-ui --action Drag --from-x --from-y --x --y --drag-speed 700 && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragStart --x --y && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 400 && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 400 && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 400 && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragEnd --x --y --drag-speed 400 && sleep 0.3 && \ -uloop simulate-mouse-ui --action Drag --from-x --from-y --x --y --drag-speed 700 && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragStart --x --y && sleep 0.3 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragMove --x --y --drag-speed 300 && sleep 0.4 && \ -uloop simulate-mouse-ui --action DragEnd --x --y --drag-speed 300 -``` - -### Step 3: Verify results - -Take a final screenshot to confirm all boxes reached the drop zone: - -```bash -uloop screenshot --capture-mode rendering -``` diff --git a/.claude/skills/uloop-clear-console/SKILL.md b/.claude/skills/uloop-clear-console/SKILL.md index 5c633ca30b..3cb9e2be3f 100644 --- a/.claude/skills/uloop-clear-console/SKILL.md +++ b/.claude/skills/uloop-clear-console/SKILL.md @@ -45,5 +45,4 @@ Returns JSON with: - `errorCount` (number): Errors cleared - `warningCount` (number): Warnings cleared - `logCount` (number): Info logs cleared -- `message` (string): Description of the result; carries the failure summary when the operation fails (e.g. `"Failed to clear console: ..."`) -- `errorMessage` (string): Currently always empty for this tool — read `message` for failure details +- `message` (string): Description of the result. On failure, this field carries the error summary. diff --git a/.claude/skills/uloop-control-play-mode/SKILL.md b/.claude/skills/uloop-control-play-mode/SKILL.md index 662c027629..bfdb9e9bc9 100644 --- a/.claude/skills/uloop-control-play-mode/SKILL.md +++ b/.claude/skills/uloop-control-play-mode/SKILL.md @@ -57,9 +57,6 @@ Returns JSON with the current play mode state: ## Notes -- Play action starts the game in the Unity Editor (also resumes from pause) -- Stop action exits play mode and returns to edit mode. If Play Mode was already stopped, `changed` is `false`, `wasAlreadyStopped` is `true`, and `message` is `Play mode was already stopped`. -- Pause action pauses the game while remaining in play mode -- Step action advances exactly one frame and leaves play mode paused (the Editor's Next Frame button; independent of Time.timeScale). Requires PlayMode to be running; repeat to walk transitions frame by frame -- Useful for automated testing workflows +- Stop on an already-stopped Editor sets `changed: false`, `wasAlreadyStopped: true` +- Step is independent of `Time.timeScale`; requires PlayMode to be running - The command waits for the requested state before returning. Increase `--timeout-seconds` for projects with slow PlayMode entry. diff --git a/.claude/skills/uloop-execute-dynamic-code/SKILL.md b/.claude/skills/uloop-execute-dynamic-code/SKILL.md index 45ce0d1acc..1b2c08ebf9 100644 --- a/.claude/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.claude/skills/uloop-execute-dynamic-code/SKILL.md @@ -18,8 +18,7 @@ This tool can inspect reachable Unity state, such as GameObjects, components, pu 1. Read the relevant reference file(s) from the Code Examples section below 2. Construct C# code based on the reference examples 3. Execute with `--code` or `--code-file` using the active shell's quoting guidance -4. If execution fails, adjust code and retry -5. Report the execution result +4. Report the execution result ## Parameters @@ -48,8 +47,7 @@ Returns JSON: - `result`: string — value of the snippet's `return` statement (empty when omitted) - `logs`: string[] — execution messages from the dynamic-code tool; read Unity Console `Debug.Log` output with `get-logs` - `compilationErrors`: object[] — Roslyn diagnostics with `message`, `line`, `column`, `errorCode`, optional `hint` and `suggestions` -- `errorMessage`: string — top-level failure summary (empty on success) -- `error`: string — alias of `errorMessage` +- `error` / `errorMessage`: string — top-level failure summary (empty on success) - `updatedCode`: string|null — the wrapped form actually compiled (handy when debugging using-statement reordering) - `diagnosticsSummary`: string|null — compact summary when diagnostics are available - `diagnostics`: object[] — structured diagnostics; same shape as `compilationErrors`, usually populated together with it diff --git a/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md index 31ddf7adc1..0dfc3876b9 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -2,54 +2,25 @@ Code examples for AssetDatabase operations using `execute-dynamic-code`. -## Find Assets by Type +## Find Assets ```csharp using UnityEditor; using System.Collections.Generic; -string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab"); -List paths = new List(); - -foreach (string guid in prefabGuids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} prefabs"; -``` - -## Find Assets by Name +// By type +string[] guids = AssetDatabase.FindAssets("t:Prefab"); +// By name +// string[] guids = AssetDatabase.FindAssets("Player"); +// By type in folder +// string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Prefabs" }); -```csharp -using UnityEditor; -using System.Collections.Generic; - -string searchName = "Player"; -string[] guids = AssetDatabase.FindAssets(searchName); List paths = new List(); - -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} assets matching '{searchName}'"; -``` - -## Find Assets in Folder - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string folder = "Assets/Prefabs"; -string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { folder }); -List paths = new List(); - foreach (string guid in guids) { paths.Add(AssetDatabase.GUIDToAssetPath(guid)); } -return $"Found {paths.Count} prefabs in {folder}"; +return $"Found {paths.Count} assets"; ``` ## Duplicate Asset @@ -76,7 +47,7 @@ string error = AssetDatabase.MoveAsset(sourcePath, destPath); return string.IsNullOrEmpty(error) ? $"Moved to {destPath}" : $"Error: {error}"; ``` -## Rename Asset +## Rename Asset (no Undo) ```csharp using UnityEditor; diff --git a/.claude/skills/uloop-execute-dynamic-code/references/cleanup-operations.md b/.claude/skills/uloop-execute-dynamic-code/references/cleanup-operations.md index 40eec9a52f..1c2e023f0a 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/cleanup-operations.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/cleanup-operations.md @@ -78,56 +78,27 @@ Undo.CollapseUndoOperations(undoGroup); return $"Removed {totalRemoved} missing scripts from scene"; ``` -## Detect Missing References in Component +## Scan for Missing References + +Scans the entire scene by default. Set `scanSelectionOnly` to `true` only when the task asks for the selected GameObject. ```csharp using UnityEditor; +bool scanSelectionOnly = false; GameObject selected = Selection.activeGameObject; -if (selected == null) +if (scanSelectionOnly && selected == null) { return "No GameObject selected"; } -List missingRefs = new List(); - -Component[] components = selected.GetComponents(); -foreach (Component comp in components) -{ - if (comp == null) continue; - - SerializedObject so = new SerializedObject(comp); - SerializedProperty prop = so.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - missingRefs.Add($"{comp.GetType().Name}.{prop.name}"); - } - } - } -} +GameObject[] targets = scanSelectionOnly + ? new[] { selected } + : Object.FindObjectsByType(FindObjectsSortMode.None); -if (missingRefs.Count == 0) -{ - return "No missing references found"; -} - -return $"Missing references: {string.Join(", ", missingRefs)}"; -``` - -## Scan Scene for Missing References - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); List results = new List(); -foreach (GameObject obj in allObjects) +foreach (GameObject obj in targets) { Component[] components = obj.GetComponents(); foreach (Component comp in components) @@ -152,10 +123,10 @@ foreach (GameObject obj in allObjects) if (results.Count == 0) { - return "No missing references found in scene"; + return "No missing references found"; } -return $"Missing references ({results.Count}): {string.Join(", ", results.Take(10))}..."; +return $"Missing references ({results.Count}): {string.Join(", ", results.Take(10))}"; ``` ## Find Unused Materials in Project @@ -196,31 +167,6 @@ foreach (string guid in materialGuids) return $"Found {unusedMaterials.Count} materials not referenced by prefabs. Verify scene and other asset references manually before deleting."; ``` -## Find Empty GameObjects - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List emptyObjects = new List(); - -foreach (GameObject obj in allObjects) -{ - Component[] components = obj.GetComponents(); - if (components.Length == 1 && obj.transform.childCount == 0) - { - emptyObjects.Add(obj.name); - } -} - -if (emptyObjects.Count == 0) -{ - return "No empty GameObjects found"; -} - -return $"Empty objects ({emptyObjects.Count}): {string.Join(", ", emptyObjects.Take(20))}"; -``` - ## Find Duplicate Names in Hierarchy ```csharp @@ -311,17 +257,13 @@ if (negativeScale.Count == 0) return $"Negative scale objects: {string.Join(", ", negativeScale.Take(10))}"; ``` -## Remove Empty Leaf GameObjects +## Find Empty Leaf GameObjects ```csharp using UnityEditor; GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Remove Empty Leaves"); - -int removedCount = 0; +List emptyObjects = new List(); foreach (GameObject obj in allObjects) { if (obj == null) continue; @@ -329,13 +271,16 @@ foreach (GameObject obj in allObjects) Component[] components = obj.GetComponents(); if (components.Length == 1 && obj.transform.childCount == 0) { - Undo.DestroyObjectImmediate(obj); - removedCount++; + emptyObjects.Add(obj.name); } } -Undo.CollapseUndoOperations(undoGroup); -return $"Removed {removedCount} empty leaf GameObjects"; +if (emptyObjects.Count == 0) +{ + return "No empty leaf GameObjects found"; +} + +return $"Empty leaf GameObjects ({emptyObjects.Count}): {string.Join(", ", emptyObjects.Take(20))}"; ``` ## Find Large Meshes diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md index f7bda52218..f33e3e68c9 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-ui-controls.md @@ -18,57 +18,34 @@ return $"Set InputField text to: {input.text}"; ## Set Slider Value -```csharp -using UnityEngine.UI; - -Slider slider = GameObject.Find("Canvas/VolumeSlider")?.GetComponent(); -if (slider == null) return "Slider not found"; - -float oldValue = slider.value; -slider.value = 0.75f; -return $"Slider changed from {oldValue} to {slider.value}"; -``` - -## Set Slider Value with Bounds Check - ```csharp using UnityEngine.UI; using System.Linq; Slider[] sliders = Object.FindObjectsByType(FindObjectsSortMode.None); -Slider target = sliders.FirstOrDefault(s => s.gameObject.name == "HealthBar"); -if (target == null) return $"HealthBar not found. Available: {string.Join(", ", sliders.Select(s => s.gameObject.name))}"; +Slider target = sliders.FirstOrDefault(s => s.gameObject.name == "VolumeSlider"); +if (target == null) return $"VolumeSlider not found. Available: {string.Join(", ", sliders.Select(s => s.gameObject.name))}"; -float targetValue = 0.5f; +float targetValue = 0.75f; float clamped = Mathf.Clamp(targetValue, target.minValue, target.maxValue); target.value = clamped; return $"Set {target.gameObject.name} to {clamped} (range: {target.minValue}-{target.maxValue})"; ``` -## Toggle On/Off - -```csharp -using UnityEngine.UI; - -Toggle toggle = GameObject.Find("Canvas/MuteToggle")?.GetComponent(); -if (toggle == null) return "Toggle not found"; - -bool oldValue = toggle.isOn; -toggle.isOn = !toggle.isOn; -return $"Toggle changed from {oldValue} to {toggle.isOn}"; -``` +## Set Toggle Value -## Set Toggle to Specific Value +Assign a specific value for deterministic setup. Use `!toggle.isOn` only when the task explicitly asks to flip the current value. ```csharp using UnityEngine.UI; using System.Linq; Toggle[] toggles = Object.FindObjectsByType(FindObjectsSortMode.None); -Toggle target = toggles.FirstOrDefault(t => t.gameObject.name == "SoundToggle"); -if (target == null) return $"SoundToggle not found. Available: {string.Join(", ", toggles.Select(t => t.gameObject.name))}"; +Toggle target = toggles.FirstOrDefault(t => t.gameObject.name == "MuteToggle"); +if (target == null) return $"MuteToggle not found. Available: {string.Join(", ", toggles.Select(t => t.gameObject.name))}"; -target.isOn = true; +bool targetValue = true; +target.isOn = targetValue; return $"Set {target.gameObject.name} to {target.isOn}"; ``` diff --git a/.claude/skills/uloop-execute-dynamic-code/references/scene-operations.md b/.claude/skills/uloop-execute-dynamic-code/references/scene-operations.md index d34db4c49f..d5e17890d4 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/scene-operations.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/scene-operations.md @@ -64,13 +64,6 @@ if (obj == null) return $"Found: {obj.name} at {obj.transform.position}"; ``` -## Find GameObjects by Tag - -```csharp -GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy"); -return $"Found {enemies.Length} GameObjects with tag 'Enemy'"; -``` - ## Set Parent ```csharp diff --git a/.claude/skills/uloop-execute-dynamic-code/references/scriptableobject.md b/.claude/skills/uloop-execute-dynamic-code/references/scriptableobject.md index debc01b9f8..1827a4fb5a 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/scriptableobject.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/scriptableobject.md @@ -33,32 +33,7 @@ return $"Created {so.GetType().Name} at {path}"; ## Modify ScriptableObject with SerializedObject -```csharp -using UnityEditor; - -string path = "Assets/Data/MyData.asset"; -ScriptableObject so = AssetDatabase.LoadAssetAtPath(path); - -if (so == null) -{ - return $"Asset not found at {path}"; -} - -SerializedObject serializedObj = new SerializedObject(so); -SerializedProperty prop = serializedObj.FindProperty("myField"); - -if (prop != null) -{ - prop.stringValue = "New Value"; - serializedObj.ApplyModifiedProperties(); - EditorUtility.SetDirty(so); - AssetDatabase.SaveAssets(); - return "Property updated"; -} -return "Property 'myField' not found"; -``` - -## Set Int/Float/Bool Properties +Use `FindProperty` with the matching typed setter (`stringValue`, `intValue`, `floatValue`, `boolValue`, etc.). ```csharp using UnityEditor; diff --git a/.claude/skills/uloop-execute-dynamic-code/references/selection-operations.md b/.claude/skills/uloop-execute-dynamic-code/references/selection-operations.md index 629c57d8b2..c23f5587f3 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/selection-operations.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/selection-operations.md @@ -65,12 +65,15 @@ Selection.objects = enemies; return $"Selected {enemies.Length} enemies"; ``` -## Get Top-Level Transforms Only +## Get Transforms by Selection Mode + +`SelectionMode.TopLevel` excludes children, `Deep` includes children, `Editable` excludes locked/immutable objects. ```csharp using UnityEditor; using System.Collections.Generic; +// TopLevel | Deep | Editable Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel); if (transforms.Length == 0) { @@ -82,45 +85,13 @@ foreach (Transform t in transforms) { names.Add(t.name); } -return $"Top-level: {string.Join(", ", names)}"; -``` - -## Get Deep Selection (Including Children) - -```csharp -using UnityEditor; - -Transform[] transforms = Selection.GetTransforms(SelectionMode.Deep); -if (transforms.Length == 0) -{ - return "No transforms selected"; -} - -return $"Deep selection count: {transforms.Length}"; -``` - -## Get Editable Objects Only - -```csharp -using UnityEditor; -using System.Collections.Generic; - -Transform[] transforms = Selection.GetTransforms(SelectionMode.Editable); -if (transforms.Length == 0) -{ - return "No editable transforms selected"; -} - -List names = new List(); -foreach (Transform t in transforms) -{ - names.Add(t.name); -} -return $"Editable: {string.Join(", ", names)}"; +return $"Selected ({transforms.Length}): {string.Join(", ", names)}"; ``` ## Get Selected Assets +Use `Selection.GetFiltered` when you need the object reference, or `Selection.assetGUIDs` when you need GUIDs. + ```csharp using UnityEditor; using System.Collections.Generic; @@ -139,26 +110,6 @@ foreach (Object asset in selectedAssets) return $"Assets: {string.Join(", ", paths)}"; ``` -## Get Selected Asset GUIDs - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string[] guids = Selection.assetGUIDs; -if (guids.Length == 0) -{ - return "No assets selected"; -} - -List paths = new List(); -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Selected assets: {string.Join(", ", paths)}"; -``` - ## Select All Children of Selected Object ```csharp diff --git a/.claude/skills/uloop-find-game-objects/SKILL.md b/.claude/skills/uloop-find-game-objects/SKILL.md index 620c44e32c..24edd909a3 100644 --- a/.claude/skills/uloop-find-game-objects/SKILL.md +++ b/.claude/skills/uloop-find-game-objects/SKILL.md @@ -77,7 +77,7 @@ Returns JSON with: - `tag` (string): GameObject tag - `layer` (number): Layer index - `components` (array): Each entry has `type` (short name, e.g., `Rigidbody`), `fullTypeName` (e.g., `UnityEngine.Rigidbody`), and `properties` (array of Inspector-visible `{name, type, value}` pairs) -- `totalFound` (number): Number of results returned inline, or number exported for multi-selection file output. For search modes, this is after `--max-results` clipping and serialization. +- `totalFound` (number): Results returned (after `--max-results` clipping). For multi-selection file export, this is the number exported. - `errorMessage` (string): Top-level failure summary (empty on success) - `processingErrors` (array): Selected-mode per-GameObject serialization failures, each `{gameObjectName, gameObjectPath, error}`. Omitted/null or empty on clean runs. diff --git a/.claude/skills/uloop-focus-window/SKILL.md b/.claude/skills/uloop-focus-window/SKILL.md index 515bfa29c3..e2ca7a23bb 100644 --- a/.claude/skills/uloop-focus-window/SKILL.md +++ b/.claude/skills/uloop-focus-window/SKILL.md @@ -13,10 +13,6 @@ Bring Unity Editor window to front using OS-level commands. uloop focus-window ``` -## Parameters - -None. - ## Global Options | Option | Description | @@ -36,11 +32,7 @@ Returns JSON with: - `success`: Whether the focus operation succeeded - `message`: Status message (e.g. `Unity Editor window focused (PID: 12345)`, or the failure reason such as `Unity project not found` / `No running Unity process found for this project` / `Failed to focus Unity window: `) -These are the only two fields. There is no PID, window-handle, or platform field returned to the caller. - ## Notes - **Works even when Unity is busy** (compiling, domain reload, etc.) -- Uses OS-level commands (osascript on macOS, PowerShell on Windows) - Useful before `uloop capture-unity-window` to ensure the target window is visible -- Brings the main Unity Editor window to the foreground diff --git a/.claude/skills/uloop-get-logs/SKILL.md b/.claude/skills/uloop-get-logs/SKILL.md index 2ecb25aff7..57d1d97f62 100644 --- a/.claude/skills/uloop-get-logs/SKILL.md +++ b/.claude/skills/uloop-get-logs/SKILL.md @@ -8,8 +8,6 @@ description: "Read current Unity Console entries from a running Editor. Use duri Retrieve logs from Unity Console. -When a pause-point marker pauses Unity and the value you need is a method local, intermediate calculation, or branch reason, read the focused `Debug.Log` entry added immediately before the marker before resuming PlayMode. Use `--search-text ` so the marker and its log are checked as one breakpoint-style proof. - ## Usage ```bash @@ -54,10 +52,7 @@ uloop get-logs --search-text "Missing.*Component" --use-regex Returns JSON with: - `totalCount` (number): Total logs available before max-count clipping - `displayedCount` (number): Logs returned in this response (≤ `--max-count`) -- `logType` (string): The `--log-type` filter that was applied -- `maxCount` (number): The `--max-count` cap that was applied -- `searchText` (string): The `--search-text` filter that was applied (empty when omitted) -- `includeStackTrace` (boolean): Whether stack traces are included in `logs[]` +- Input filters (`logType`, `maxCount`, `searchText`, `includeStackTrace`) are echoed back in the response - `logs` (array): Each entry has: - `type` (string): `"Error"`, `"Warning"`, or `"Log"` - `message` (string): Log message body diff --git a/.claude/skills/uloop-launch/SKILL.md b/.claude/skills/uloop-launch/SKILL.md index 2bf0e0929b..7bbee3d3e5 100644 --- a/.claude/skills/uloop-launch/SKILL.md +++ b/.claude/skills/uloop-launch/SKILL.md @@ -47,22 +47,24 @@ uloop launch --quit ## Output -- Prints detected Unity version -- Prints project path +May print status/progress lines before the final JSON payload, such as project path, detected Unity version, or readiness wait messages. + +The final JSON payload includes: +- `success`: whether the command completed +- `ready`: whether Unity CLI Loop is ready for commands +- `serverReady`: whether the Unity CLI Loop server accepted requests +- `projectIpcReady`: whether the project IPC path accepted tool requests +- `alreadyRunning`: whether an existing Unity process was reused +- `launched`: whether this command launched a Unity process +- `restarted`: whether this command stopped an existing process and launched a new one +- `quit`: whether this command stopped Unity without launching a new process +- `previousProcessId`: process ID stopped by restart or quit, when available +- `currentProcessId`: current Unity process ID, when available +- `projectRoot`: resolved project root +- `message`: readiness summary + +## Notes + - If Unity is already running, focuses the existing window and verifies tool readiness -- If the process scan is blocked by the environment (e.g. sandboxed `ps`), plain launch falls back to probing the project IPC; when Unity responds it reports `alreadyRunning: true` without focusing the window instead of failing. `--restart` and `--quit` still fail because they need the process id -- If launching or restarting, prints when it is waiting for Unity CLI Loop server readiness -- If launching or restarting, waits until Unity finishes startup and the CLI can connect to the project -- Successful launch, restart, existing-process, and quit paths return JSON with: - - `success`: whether the command completed - - `ready`: whether Unity CLI Loop is ready for commands - - `serverReady`: whether the Unity CLI Loop server accepted requests - - `projectIpcReady`: whether the project IPC path accepted tool requests - - `alreadyRunning`: whether an existing Unity process was reused - - `launched`: whether this command launched a Unity process - - `restarted`: whether this command stopped an existing process and launched a new one - - `quit`: whether this command stopped Unity without launching a new process - - `previousProcessId`: process ID stopped by restart or quit, when available - - `currentProcessId`: current Unity process ID, when available - - `projectRoot`: resolved project root - - `message`: readiness summary +- If process scan is blocked (e.g. sandboxed `ps`), plain launch falls back to IPC probing; `--restart` and `--quit` still fail because they need the process id +- The command waits until Unity finishes startup and the CLI can connect before returning diff --git a/.claude/skills/uloop-record-input/SKILL.md b/.claude/skills/uloop-record-input/SKILL.md index 6b1c530f38..a76d38cb33 100644 --- a/.claude/skills/uloop-record-input/SKILL.md +++ b/.claude/skills/uloop-record-input/SKILL.md @@ -39,8 +39,7 @@ Replay injects input frame-by-frame, so the game must also be deterministic to p ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) -- Use this only when the project already uses the New Input System. +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. ## Output @@ -52,4 +51,4 @@ The CLI prints JSON with: - `totalFrames`: Number of frames recorded (nullable int; populated on `Stop` only) - `durationSeconds`: Recording duration in seconds (nullable float; populated on `Stop` only) -The CLI output contains only these six payload fields. Internal metadata such as `ver` is removed before printing. There is no `recordingId`, `startTimestamp`, `keysCaptured`, or per-frame data in the response — frame data lives only in the JSON file at `outputPath`. +Frame data lives only in the JSON file at `outputPath`; internal metadata fields are not included in the response. diff --git a/.claude/skills/uloop-replay-input/SKILL.md b/.claude/skills/uloop-replay-input/SKILL.md index d69e1ad07e..81793dbc8e 100644 --- a/.claude/skills/uloop-replay-input/SKILL.md +++ b/.claude/skills/uloop-replay-input/SKILL.md @@ -43,8 +43,7 @@ Replay injects the exact same input frame-by-frame, but the game must also be de ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) -- Use this only when the project already uses the New Input System. +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. ## Output @@ -57,5 +56,3 @@ Returns JSON with: - `totalFrames`: Total frames in the recording (nullable int) - `progress`: Replay progress (nullable float in 0.0 – 1.0) - `isReplaying`: Whether replay is currently active (nullable bool) - -These are the only eight fields. There is no `loopCount`, `elapsedSeconds`, `overlayVisible`, or per-frame inspection data in the response. diff --git a/.claude/skills/uloop-run-tests/SKILL.md b/.claude/skills/uloop-run-tests/SKILL.md index ba6909c219..7660586e11 100644 --- a/.claude/skills/uloop-run-tests/SKILL.md +++ b/.claude/skills/uloop-run-tests/SKILL.md @@ -8,11 +8,11 @@ description: "Run Unity Test Runner and report detailed results. Use for EditMod Execute Unity Test Runner. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `xmlPath` for detailed failure diagnosis. -Before running `uloop run-tests`, run `uloop compile` for the same Unity project when the current task created, deleted, renamed, moved, or edited C# source files, test files, `.asmdef`, `.asmref`, package manifest files, or scripting define settings. This refreshes the AssetDatabase, lets Unity discover new tests, and surfaces compile errors before test execution. You may skip this compile step when rerunning tests without code or assembly-definition changes since the last successful compile. +Before running tests, run `uloop compile` first if you created, deleted, renamed, moved, or edited C# files, test files, `.asmdef`/`.asmref`, or package manifests since the last successful compile. This refreshes the AssetDatabase and surfaces compile errors before test execution. Before executing tests, `uloop run-tests` saves unsaved loaded Scene changes and unsaved current Prefab Stage changes by default. If saving fails, it returns `success: false`, keeps `testCount` at `0`, lists the unsaved items in `message`, and does not start the Unity Test Runner. -When a run returns `status: "NoTestsFound"` or `noTestsFound: true`, no matching tests were discovered. This is not the same as a failed test case: `hasFailures` remains `false`, `failedCount` remains `0`, and `noTestsFoundExplanation` explains the zero-discovery state for agents. When an unfiltered `--filter-type all` run also has `message` set to the no-tests message, read the `message` for asmdef hints. `run-tests` only appends those hints when it detects likely test assembly configuration issues; exact, regex, and assembly filter misses keep the plain no-tests message. +`NoTestsFound` means zero tests matched — not a test failure. Check `noTestsFoundExplanation` and `message` for asmdef hints. ## Usage diff --git a/.claude/skills/uloop-screenshot/SKILL.md b/.claude/skills/uloop-screenshot/SKILL.md index d10d199ea2..bc721a5ee7 100644 --- a/.claude/skills/uloop-screenshot/SKILL.md +++ b/.claude/skills/uloop-screenshot/SKILL.md @@ -67,9 +67,6 @@ uloop screenshot --window-name Project --match-mode prefix # Save screenshot to a specific directory uloop screenshot --output-directory /tmp/screenshots - -# Combine options -uloop screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots ``` ## Output diff --git a/.claude/skills/uloop-simulate-keyboard/SKILL.md b/.claude/skills/uloop-simulate-keyboard/SKILL.md index 6cb374cc93..7b65d6b135 100644 --- a/.claude/skills/uloop-simulate-keyboard/SKILL.md +++ b/.claude/skills/uloop-simulate-keyboard/SKILL.md @@ -42,16 +42,11 @@ uloop simulate-keyboard --action --key [options] Use `Press` for edge-triggered keyboard code such as `Keyboard.current.spaceKey.wasPressedThisFrame`. `KeyDown` emits one initial press edge, then only keeps the key held. It does not keep `wasPressedThisFrame` true while the key remains held. If a successful `Press` or `KeyDown` leaves `Keyboard.current..isPressed` true but runtime state does not change, do not immediately rewrite the user's runtime code to `isPressed`. First verify that the target component is active during the command, that it polls input in the configured Input System update phase, and that a missed `KeyDown` edge is followed by `KeyUp` before retrying. -Use `KeyDown` / `KeyUp` when the scenario intentionally needs a held key. - ### Pause Point Inspection (Standard for E2E) -- Use `UloopPausePoint.Pause("")` with `uloop-wait-for-pause-point` as the standard frame proof when this input drives a state transition you are verifying. Final logs, screenshots, and durable state supplement the paused-frame check but do not replace it. -- Put the marker at a natural state transition after the app consumed the key, such as after a command is accepted, a state mutation is committed, an evaluation step resolves, or a dependent component is updated. Do not place it immediately after sending `simulate-keyboard`. -- If the key handler has local variables, intermediate calculations, or branch reasons that `execute-dynamic-code` cannot inspect after the fact, log just those values near `UloopPausePoint.Pause("")` and read them with `uloop-get-logs` while Unity is paused. A pause point hit proves the line was reached, not the frame-local values. -- Treat `simulate-keyboard success=true`, generic action logs, and final durable counters as useful evidence, but not as paused-frame proof. -- If the response has `interruptedByPausePoint: true`, Unity is paused for inspection and the tool released its held input bookkeeping. If a `UloopPausePoint.Pause` marker caused the pause, `pausePointId` and `pausePointHitCount` identify it. Use `uloop get-logs`, `uloop get-hierarchy`, `uloop find-game-objects`, or `uloop execute-dynamic-code` before resuming. -- Use distinct marker ids for strict phases, for example `input-read`, `state-updated`, and `result-committed`. +For standard frame proof when this input drives a state transition, follow the `uloop-wait-for-pause-point` skill. Place markers after the app consumed the key, not immediately after `simulate-keyboard`. + +- If `interruptedByPausePoint: true`, Unity is paused and input bookkeeping was released. `pausePointId` and `pausePointHitCount` identify the marker. `pressEdgeObserved` is still reported on pause-point interruptions. ### KeyDown/KeyUp Rules @@ -103,6 +98,5 @@ Returns JSON with: ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) -- Use this only when the project already uses the New Input System. +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. - Game code must read input via Input System API (e.g. `Keyboard.current[Key.W].isPressed`), not legacy `Input.GetKey()` diff --git a/.claude/skills/uloop-simulate-mouse-input/SKILL.md b/.claude/skills/uloop-simulate-mouse-input/SKILL.md index 12b73569e4..8bc5b09e69 100644 --- a/.claude/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-input/SKILL.md @@ -50,11 +50,9 @@ uloop simulate-mouse-input --action [options] ### Pause Point Inspection (Standard for E2E) -- Use `UloopPausePoint.Pause("")` with `uloop-wait-for-pause-point` as the standard frame proof when this input drives a state transition you are verifying. Final logs, screenshots, and durable state supplement the paused-frame check but do not replace it. -- Place the pause point at a natural state transition after the app consumed the mouse input, such as after a command is accepted, a state mutation is committed, an evaluation step resolves, a tracked value changes, or a dependent component is updated. Do not place it immediately after sending `simulate-mouse-input`. -- If the mouse handler has local variables, intermediate calculations, or branch reasons that `execute-dynamic-code` cannot inspect after the fact, log just those values near `UloopPausePoint.Pause("")` and read them with `uloop-get-logs` while Unity is paused. A pause point hit proves the line was reached, not the frame-local values. -- If the response has `interruptedByPausePoint: true`, Unity is paused for inspection and the tool released its held input bookkeeping. `pausePointId` and `pausePointHitCount` identify the pause point that paused Unity. Use `uloop get-logs`, `uloop get-hierarchy`, `uloop find-game-objects`, or `uloop execute-dynamic-code` before resuming. -- Use distinct ids for strict phases, for example `input-read`, `state-updated`, and `result-committed`. +For standard frame proof when this input drives a state transition, follow the `uloop-wait-for-pause-point` skill. Place markers after the app consumed the mouse input, not immediately after `simulate-mouse-input`. + +- If `interruptedByPausePoint: true`, Unity is paused and input bookkeeping was released. `pausePointId` and `pausePointHitCount` identify the marker. - Remove temporary pause-point/log instrumentation before final validation when it was added only for inspection. ### Global Options (optional) @@ -66,14 +64,16 @@ uloop simulate-mouse-input --action [options] ## When to use this vs simulate-mouse-ui +All rows below assume the New Input System is installed. + | Scenario | Tool | |----------|------| | Click a Unity UI Button (IPointerClickHandler) | `simulate-mouse-ui` | -| Runtime logic reads `Mouse.current.leftButton` | `simulate-mouse-input` when the project uses the New Input System | -| Runtime logic reads right-click | `simulate-mouse-input --button Right` when the project uses the New Input System | +| Runtime logic reads `Mouse.current.leftButton` | `simulate-mouse-input` | +| Runtime logic reads right-click | `simulate-mouse-input --button Right` | | Drag a UI slider | `simulate-mouse-ui --action Drag` | -| Runtime logic reads `Mouse.current.delta` | `simulate-mouse-input --action MoveDelta` when the project uses the New Input System | -| Runtime logic reads `Mouse.current.scroll` | `simulate-mouse-input --action Scroll` when the project uses the New Input System | +| Runtime logic reads `Mouse.current.delta` | `simulate-mouse-input --action MoveDelta` | +| Runtime logic reads `Mouse.current.scroll` | `simulate-mouse-input --action Scroll` | ## Examples @@ -103,9 +103,8 @@ uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --dura ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** must be installed (`com.unity.inputsystem`) +- **Input System package** (`com.unity.inputsystem`) must be installed; this tool only works with the New Input System. - Game code must read input via Input System API (e.g. `Mouse.current.leftButton.wasPressedThisFrame`) -- Use this only when the project already uses the New Input System. ## Output @@ -121,4 +120,4 @@ Returns JSON with: - `pausePointHitCount`: The hit count for that `UloopPausePoint.Pause("")` - `pausePointHits` (array, nullable): Every marker hit during this input as `{id, hitCount}` entries, in hit order. Read this when one input may trigger several markers; `pausePointId` only names the latest one -There is no `deltaX`, `deltaY`, `scrollX`, `scrollY`, `duration`, or hit-element field in the response — only the issued action, button, target position, and Pause Point interruption state are echoed back. Verify visual outcome with a follow-up screenshot. +Verify visual outcome with a follow-up screenshot. diff --git a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md index f5e2799aec..8a9a797b6b 100644 --- a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md @@ -57,7 +57,6 @@ uloop simulate-mouse-ui --action --x --y [options] - `DragStart` must be called before `DragMove` or `DragEnd` - `DragEnd` must be called to release an active drag — failing to call it leaves drag state stuck - Calling `DragMove` or `DragEnd` without an active drag returns an error -- `Drag`, `DragStart`, `DragMove`, and `DragEnd` only support `--button Left` ### Global Options (all optional, mutually exclusive) @@ -78,11 +77,8 @@ uloop simulate-mouse-ui --action --x --y [options] ## Pause Point Inspection (Standard for E2E) -- Use `UloopPausePoint.Pause("")` with `uloop wait-for-pause-point` as the standard frame proof when this input drives a state transition you are verifying. Final logs, screenshots, and durable state supplement the paused-frame check but do not replace it. -- Place the pause point at a natural transition after the app consumed the UI event, such as after a command is accepted, a state mutation is committed, a tracked value changes, a UI/domain state syncs, or a success/failure/end condition is entered. -- If the UI handler has local variables, intermediate calculations, or branch reasons that `uloop execute-dynamic-code` cannot inspect after the fact, log just those values near `UloopPausePoint.Pause("")` and read them with `uloop get-logs` while Unity is paused. A pause point hit proves the line was reached, not the frame-local values. -- Treat `uloop simulate-mouse-ui` `success=true`, generic action logs, and final durable counters as useful evidence, not paused-frame proof. -- If a `UloopPausePoint.Pause` pauses Unity, inspect with `uloop get-logs`, `uloop get-hierarchy`, `uloop find-game-objects`, or `uloop execute-dynamic-code` before resuming. +For standard frame proof when this UI input drives a state transition, follow the `uloop-wait-for-pause-point` skill. Place markers after the app consumed the UI event, not immediately after `simulate-mouse-ui`. + - Remove temporary pause-point/log instrumentation before final validation when it was added only for inspection. ## Examples @@ -138,6 +134,6 @@ Returns JSON with: - `endPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `endPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `button`, `duration`, `dragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +Verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. Note: Click and LongPress on empty space (no UI element) still return `success = true` with `hitGameObjectName = null`. Drag actions on empty space return `success = false`. diff --git a/.claude/skills/uloop-wait-for-pause-point/SKILL.md b/.claude/skills/uloop-wait-for-pause-point/SKILL.md index 2ac9b2c58b..14699e0deb 100644 --- a/.claude/skills/uloop-wait-for-pause-point/SKILL.md +++ b/.claude/skills/uloop-wait-for-pause-point/SKILL.md @@ -47,7 +47,6 @@ Use `generation`, `enabledAtUtc`, and the hit sequence fields from the hit or st - Consider a pause point during E2E passes when transition-frame evidence would add confidence, even if durable state, logs, or screenshots can later confirm the final result. - Use this before reaching for `Time.timeScale`, sleeps, repeated polling, or after-the-fact `execute-dynamic-code`; those checks can supplement the paused-frame proof, but they are not substitutes. - If the value you need is a method local, an intermediate calculation, or a branch reason that `execute-dynamic-code` cannot reach, add a focused `Debug.Log` immediately before the marker and read it with `get-logs` while paused. Do not count the breakpoint check complete until the matching log has been read. -- Good pause points include after input is consumed, a command is accepted, a state mutation is committed, an evaluation step resolves, a tracked value changes, a UI/domain state syncs, or a success/failure/end condition is entered. - Treat the pause like a lightweight breakpoint for one important transition: combine nearby debug logs with paused-frame inspection to confirm the variables and component state at that point. - Do not treat `simulate-* success=true`, generic action logs, sleeps/retries, testing-only counters, or `Time.timeScale` changes as paused-frame proof. - Skip this only for ordinary persistent-state checks when you are not validating simulated input delivery, event ordering, or transition-frame fidelity. diff --git a/.codex/skills/uloop-clear-console/SKILL.md b/.codex/skills/uloop-clear-console/SKILL.md deleted file mode 100644 index 5c633ca30b..0000000000 --- a/.codex/skills/uloop-clear-console/SKILL.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: uloop-clear-console -toolName: clear-console -description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." ---- - -# uloop clear-console - -Clear Unity console logs. - -## Usage - -```bash -uloop clear-console [--add-confirmation-message] -``` - -## Parameters - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `--add-confirmation-message` | flag | - | Add confirmation message after clearing | - -## Global Options - -| Option | Description | -|--------|-------------| -| `--project-path ` | Optional. Use only when the target Unity project is not the current directory. | - -## Examples - -```bash -# Clear console -uloop clear-console - -# Clear with confirmation -uloop clear-console --add-confirmation-message -``` - -## Output - -Returns JSON with: -- `success` (boolean): Whether the clear operation succeeded -- `clearedLogCount` (number): Total number of log entries that were cleared -- `clearedCounts` (object): Breakdown by log type - - `errorCount` (number): Errors cleared - - `warningCount` (number): Warnings cleared - - `logCount` (number): Info logs cleared -- `message` (string): Description of the result; carries the failure summary when the operation fails (e.g. `"Failed to clear console: ..."`) -- `errorMessage` (string): Currently always empty for this tool — read `message` for failure details diff --git a/.codex/skills/uloop-compile/SKILL.md b/.codex/skills/uloop-compile/SKILL.md deleted file mode 100644 index 9a2780a2a1..0000000000 --- a/.codex/skills/uloop-compile/SKILL.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -name: uloop-compile -toolName: compile -description: "Compile the Unity project and report errors/warnings. Use after C# edits." ---- - -# uloop compile - -Execute Unity project compilation. - -## Usage - -```bash -uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-external-scene-changes] -``` - -## Parameters - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `--force-recompile` | flag | - | Use for broader validation, including warnings hidden by other asmdefs; much slower than normal compile | -| `--no-wait-for-domain-reload` | flag | - | Return before Domain Reload completion | -| `--stop-on-external-scene-changes` | flag | - | Stop before compilation if open Scene files changed externally instead of auto-reloading them | - -## Global Options - -| Option | Description | -|--------|-------------| -| `--project-path ` | Optional. Use only when the target Unity project is not the current directory. | - -## Examples - -```bash -# Check compilation -uloop compile - -# Start compilation without waiting for Domain Reload completion -uloop compile --no-wait-for-domain-reload - -# Stop instead of auto-reloading externally changed open Scene files -uloop compile --stop-on-external-scene-changes -``` - -## Output - -Returns JSON: -- `success`: boolean or null -- `errorCount`: number or null -- `warningCount`: number or null -- `message`: string diff --git a/.codex/skills/uloop-control-play-mode/SKILL.md b/.codex/skills/uloop-control-play-mode/SKILL.md deleted file mode 100644 index 662c027629..0000000000 --- a/.codex/skills/uloop-control-play-mode/SKILL.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: uloop-control-play-mode -toolName: control-play-mode -description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." ---- - -# uloop control-play-mode - -Control Unity Editor play mode (play/stop/pause). - -## Usage - -```bash -uloop control-play-mode [options] -``` - -## Parameters - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `--action` | string | `Play` | Action to perform: `Play`, `Stop`, `Pause`, `Step` | -| `--timeout-seconds` | integer | `180` | Maximum seconds to wait for the requested play mode state | - -## Global Options - -| Option | Description | -|--------|-------------| -| `--project-path ` | Optional. Use only when the target Unity project is not the current directory. | - -## Examples - -```bash -# Start play mode -uloop control-play-mode --action Play - -# Start play mode with a longer wait budget -uloop control-play-mode --action Play --timeout-seconds 600 - -# Stop play mode -uloop control-play-mode --action Stop - -# Pause play mode -uloop control-play-mode --action Pause - -# Advance exactly one frame while paused (Next Frame button) -uloop control-play-mode --action Step -``` - -## Output - -Returns JSON with the current play mode state: -- `isPlaying`: Whether Unity is currently in play mode -- `isPaused`: Whether play mode is paused -- `changed`: Whether the requested action changed the current play mode state -- `wasAlreadyStopped`: Whether `Stop` was requested while Play Mode was already stopped -- `message`: Description of the action performed - -## Notes - -- Play action starts the game in the Unity Editor (also resumes from pause) -- Stop action exits play mode and returns to edit mode. If Play Mode was already stopped, `changed` is `false`, `wasAlreadyStopped` is `true`, and `message` is `Play mode was already stopped`. -- Pause action pauses the game while remaining in play mode -- Step action advances exactly one frame and leaves play mode paused (the Editor's Next Frame button; independent of Time.timeScale). Requires PlayMode to be running; repeat to walk transitions frame by frame -- Useful for automated testing workflows -- The command waits for the requested state before returning. Increase `--timeout-seconds` for projects with slow PlayMode entry. diff --git a/.codex/skills/uloop-execute-dynamic-code/SKILL.md b/.codex/skills/uloop-execute-dynamic-code/SKILL.md deleted file mode 100644 index 45ce0d1acc..0000000000 --- a/.codex/skills/uloop-execute-dynamic-code/SKILL.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -name: uloop-execute-dynamic-code -toolName: execute-dynamic-code -description: "Execute C# with Unity APIs when existing uloop tools cannot inspect or edit enough. Use for reachable scene/component state, scene/prefab/menu automation, and PlayMode checks" -context: fork ---- - -# Task - -Run focused C# snippets in the active Unity Editor with `uloop execute-dynamic-code`. - -For basic selected GameObject discovery or property inspection, use `find-game-objects --search-mode Selected` before this tool. Use this tool after the built-in inspection tools are not enough or when you need to modify Unity state. - -This tool can inspect reachable Unity state, such as GameObjects, components, public properties, static values, and method results. It cannot directly read local variables or intermediate calculations inside an already-running method. When those values matter, add a focused `Debug.Log` immediately before `UloopPausePoint.Pause("")`, then run `get-logs --search-text ` while Unity is paused. Do not replace that log read with execute-dynamic-code. - -## Workflow - -1. Read the relevant reference file(s) from the Code Examples section below -2. Construct C# code based on the reference examples -3. Execute with `--code` or `--code-file` using the active shell's quoting guidance -4. If execution fails, adjust code and retry -5. Report the execution result - -## Parameters - -- `--code ''`: Inline C# statements to execute. Use direct statements only; `return` is optional, and `using` directives may appear at the top of the snippet. -- `--code-file `: Read the C# statements from a file instead of `--code`. Use this when the active shell or launcher cannot preserve inline code exactly. Exactly one of `--code` or `--code-file` is required; combining them is an error. -- **Shell-specific quoting**: Read [references/playmode-automation-powershell.md](references/playmode-automation-powershell.md) for Windows/PowerShell multiline commands and [references/playmode-automation-zsh.md](references/playmode-automation-zsh.md) for zsh/macOS examples. -- `--parameters {}` (advanced, optional): Pass a shell-quoted JSON object literal when reusing a snippet with varying data or when keeping values outside the code. Values are exposed as `parameters["param0"]`, `parameters["param1"]`, and so on. Omit this flag for most snippets. Do not pass a JSON string value such as `"{\"param0\":\"value\"}"`. -- `--wait-for-domain-reload` (optional): Wait for Domain Reload recovery after snippets that intentionally trigger Unity script reload or import work. Omit this for normal inspection and editor-state workflows. - -## Code Rules - -Write direct statements only — no class/namespace/method wrappers. Return is optional. - -```csharp -using UnityEngine; -float x = Mathf.PI; -return x; -``` - -Prefer terminal commands for file operations and keep snippets focused on Unity Editor state that existing uloop tools cannot inspect or change. - -## Output - -Returns JSON: -- `success`: boolean — overall execution success -- `result`: string — value of the snippet's `return` statement (empty when omitted) -- `logs`: string[] — execution messages from the dynamic-code tool; read Unity Console `Debug.Log` output with `get-logs` -- `compilationErrors`: object[] — Roslyn diagnostics with `message`, `line`, `column`, `errorCode`, optional `hint` and `suggestions` -- `errorMessage`: string — top-level failure summary (empty on success) -- `error`: string — alias of `errorMessage` -- `updatedCode`: string|null — the wrapped form actually compiled (handy when debugging using-statement reordering) -- `diagnosticsSummary`: string|null — compact summary when diagnostics are available -- `diagnostics`: object[] — structured diagnostics; same shape as `compilationErrors`, usually populated together with it - -On `success: false`, inspect `compilationErrors` first. If empty, read `errorMessage` (and `logs` for extra context) — the failure may be a runtime exception, cancellation, or an "execution in progress" rejection, all of which return empty `compilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. - -## Code Examples by Category - -For detailed code examples, refer to these files: - -- **Prefab operations**: See [references/prefab-operations.md](references/prefab-operations.md) - - Create prefabs, instantiate, add components, modify properties -- **Material operations**: See [references/material-operations.md](references/material-operations.md) - - Create materials, set shaders/textures, modify properties -- **Asset operations**: See [references/asset-operations.md](references/asset-operations.md) - - Find/search assets, duplicate, move, rename, load -- **ScriptableObject**: See [references/scriptableobject.md](references/scriptableobject.md) - - Create ScriptableObjects, modify with SerializedObject -- **Scene operations**: See [references/scene-operations.md](references/scene-operations.md) - - Create/modify GameObjects, set parents, wire references, load scenes -- **Batch operations**: See [references/batch-operations.md](references/batch-operations.md) - - Bulk modify objects, batch add/remove components, rename, layer/tag/material replacement -- **Cleanup operations**: See [references/cleanup-operations.md](references/cleanup-operations.md) - - Detect broken scripts, missing references, unused materials, empty GameObjects -- **Undo operations**: See [references/undo-operations.md](references/undo-operations.md) - - Undo-aware operations: RecordObject, AddComponent, SetParent, grouping -- **Selection operations**: See [references/selection-operations.md](references/selection-operations.md) - - Get/set selection, multi-select, filter by type/editability -- **PlayMode automation (PowerShell/Windows)**: See [references/playmode-automation-powershell.md](references/playmode-automation-powershell.md) - - Click UI buttons, invoke methods, set fields, tool combination workflows for PowerShell users -- **PlayMode automation (zsh/macOS)**: See [references/playmode-automation-zsh.md](references/playmode-automation-zsh.md) - - Click UI buttons, invoke methods, set fields, tool combination workflows for zsh users -- **PlayMode UI controls**: See [references/playmode-ui-controls.md](references/playmode-ui-controls.md) - - InputField, Slider, Toggle, Dropdown, drag & drop simulation, list all UI controls -- **PlayMode inspection**: See [references/playmode-inspection.md](references/playmode-inspection.md) - - Scene info, game state via reflection, physics state, raycast checks, GameObject search, position/rotation diff --git a/.codex/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.codex/skills/uloop-execute-dynamic-code/references/asset-operations.md deleted file mode 100644 index 31ddf7adc1..0000000000 --- a/.codex/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ /dev/null @@ -1,194 +0,0 @@ -# Asset Operations - -Code examples for AssetDatabase operations using `execute-dynamic-code`. - -## Find Assets by Type - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab"); -List paths = new List(); - -foreach (string guid in prefabGuids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} prefabs"; -``` - -## Find Assets by Name - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string searchName = "Player"; -string[] guids = AssetDatabase.FindAssets(searchName); -List paths = new List(); - -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} assets matching '{searchName}'"; -``` - -## Find Assets in Folder - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string folder = "Assets/Prefabs"; -string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { folder }); -List paths = new List(); - -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} prefabs in {folder}"; -``` - -## Duplicate Asset - -```csharp -using UnityEditor; - -string sourcePath = "Assets/Materials/MyMaterial.mat"; -string destPath = "Assets/Materials/MyMaterial_Backup.mat"; - -bool success = AssetDatabase.CopyAsset(sourcePath, destPath); -return success ? $"Copied to {destPath}" : "Copy failed"; -``` - -## Move Asset - -```csharp -using UnityEditor; - -string sourcePath = "Assets/OldFolder/MyAsset.asset"; -string destPath = "Assets/NewFolder/MyAsset.asset"; - -string error = AssetDatabase.MoveAsset(sourcePath, destPath); -return string.IsNullOrEmpty(error) ? $"Moved to {destPath}" : $"Error: {error}"; -``` - -## Rename Asset - -```csharp -using UnityEditor; - -string assetPath = "Assets/Materials/OldName.mat"; -string newName = "NewName"; - -string error = AssetDatabase.RenameAsset(assetPath, newName); -return string.IsNullOrEmpty(error) ? $"Renamed to {newName}" : $"Error: {error}"; -``` - -## Rename Asset (Undo-supported) - -```csharp -using UnityEditor; - -// ObjectNames.SetNameSmart() supports Undo (AssetDatabase.RenameAsset() does NOT) -Object selected = Selection.activeObject; -if (selected == null) -{ - return "No asset selected"; -} - -string oldName = selected.name; -ObjectNames.SetNameSmart(selected, "NewName"); -AssetDatabase.SaveAssets(); -return $"Renamed {oldName} to {selected.name}"; -``` - -## Get Asset Path from Object - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No object selected"; -} - -string path = AssetDatabase.GetAssetPath(selected); -if (string.IsNullOrEmpty(path)) -{ - return "Selected object is not an asset (scene object)"; -} -return $"Asset path: {path}"; -``` - -## Load Asset at Path - -```csharp -using UnityEditor; - -string path = "Assets/Prefabs/Player.prefab"; -GameObject asset = AssetDatabase.LoadAssetAtPath(path); - -if (asset == null) -{ - return $"Asset not found at {path}"; -} -return $"Loaded: {asset.name}"; -``` - -## Get All Assets of Type - -```csharp -using UnityEditor; - -string[] scriptGuids = AssetDatabase.FindAssets("t:MonoScript"); -int count = 0; - -foreach (string guid in scriptGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - if (path.StartsWith("Assets/")) - { - count++; - } -} -return $"Found {count} scripts in Assets folder"; -``` - -## Check if Asset Exists - -```csharp -using UnityEditor; - -string path = "Assets/Prefabs/Player.prefab"; -string guid = AssetDatabase.AssetPathToGUID(path); - -bool exists = !string.IsNullOrEmpty(guid); -return exists ? $"Asset exists: {path}" : $"Asset not found: {path}"; -``` - -## Get Asset Dependencies - -```csharp -using UnityEditor; - -string assetPath = "Assets/Prefabs/Player.prefab"; -string[] dependencies = AssetDatabase.GetDependencies(assetPath, true); - -return $"Asset has {dependencies.Length} dependencies"; -``` - -## Refresh AssetDatabase - -Use this after terminal-based asset file changes when Unity needs to import them and generate `.meta` files. - -```csharp -using UnityEditor; - -AssetDatabase.Refresh(); -return "AssetDatabase refreshed"; -``` diff --git a/.codex/skills/uloop-execute-dynamic-code/references/batch-operations.md b/.codex/skills/uloop-execute-dynamic-code/references/batch-operations.md deleted file mode 100644 index 875c3fc65d..0000000000 --- a/.codex/skills/uloop-execute-dynamic-code/references/batch-operations.md +++ /dev/null @@ -1,399 +0,0 @@ -# Batch Operations - -Code examples for batch processing using `execute-dynamic-code`. - -## Batch Modify Selected Objects - -```csharp -using UnityEditor; -using System.Collections.Generic; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Modify"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj.transform, ""); - obj.transform.localScale = Vector3.one * 2; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Scaled {selected.Length} objects (Single undo step)"; -``` - -## Edit Multiple Objects with SerializedObject - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -List transforms = new List(); -foreach (GameObject obj in selected) -{ - transforms.Add(obj.transform); -} - -SerializedObject serializedObj = new SerializedObject(transforms.ToArray()); -SerializedProperty positionProp = serializedObj.FindProperty("m_LocalPosition"); -positionProp.vector3Value = Vector3.zero; -serializedObj.ApplyModifiedProperties(); - -return $"Reset position of {selected.Length} objects"; -``` - -## Batch Add Component - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Add Rigidbody"); - -int addedCount = 0; -foreach (GameObject obj in selected) -{ - if (obj.GetComponent() == null) - { - Undo.AddComponent(obj); - addedCount++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Added Rigidbody to {addedCount} objects"; -``` - -## Batch Process Assets with StartAssetEditing - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:Material", new[] { "Assets/Materials" }); -if (guids.Length == 0) -{ - return "No materials found"; -} - -AssetDatabase.StartAssetEditing(); -try -{ - int modified = 0; - foreach (string guid in guids) - { - string path = AssetDatabase.GUIDToAssetPath(guid); - Material mat = AssetDatabase.LoadAssetAtPath(path); - if (mat != null) - { - mat.color = Color.white; - EditorUtility.SetDirty(mat); - modified++; - } - } - - AssetDatabase.SaveAssets(); - return $"Reset color of {modified} materials"; -} -finally -{ - AssetDatabase.StopAssetEditing(); -} - -``` - -## Batch Rename GameObjects - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Rename"); - -for (int i = 0; i < selected.Length; i++) -{ - Undo.RecordObject(selected[i], ""); - selected[i].name = $"Item_{i:D3}"; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Renamed {selected.Length} objects"; -``` - -## Batch Set Layer - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int layer = LayerMask.NameToLayer("Default"); - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Set Layer"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj, ""); - obj.layer = layer; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Set layer of {selected.Length} objects to Default"; -``` - -## Batch Set Tag - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Set Tag"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj, ""); - obj.tag = "Enemy"; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Tagged {selected.Length} objects as Enemy"; -``` - -## Batch Modify ScriptableObjects - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { "Assets/Data" }); -if (guids.Length == 0) -{ - return "No ScriptableObjects found"; -} - -int modified = 0; -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - ScriptableObject so = AssetDatabase.LoadAssetAtPath(path); - if (so == null) continue; - - SerializedObject serializedObj = new SerializedObject(so); - SerializedProperty prop = serializedObj.FindProperty("isEnabled"); - if (prop != null) - { - prop.boolValue = true; - serializedObj.ApplyModifiedProperties(); - EditorUtility.SetDirty(so); - modified++; - } -} - -AssetDatabase.SaveAssets(); -return $"Enabled {modified} ScriptableObjects"; -``` - -## Batch Remove Component - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Remove Rigidbody"); - -int removedCount = 0; -foreach (GameObject obj in selected) -{ - Rigidbody rb = obj.GetComponent(); - if (rb != null) - { - Undo.DestroyObjectImmediate(rb); - removedCount++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Removed Rigidbody from {removedCount} objects"; -``` - -## Batch Set Static Flags - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Set Static"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj, ""); - GameObjectUtility.SetStaticEditorFlags(obj, StaticEditorFlags.BatchingStatic | StaticEditorFlags.OccludeeStatic); -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Set static flags on {selected.Length} objects"; -``` - -## Batch Process with Progress Bar - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:Texture2D"); -if (guids.Length == 0) -{ - return "No textures found"; -} - -int processed = 0; -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; - if (importer != null && importer.maxTextureSize > 1024) - { - importer.maxTextureSize = 1024; - importer.SaveAndReimport(); - processed++; - } - - if (processed % 10 == 0) - { - EditorUtility.DisplayProgressBar("Processing Textures", path, (float)processed / guids.Length); - } -} - -EditorUtility.ClearProgressBar(); -return $"Resized {processed} textures to max 1024"; -``` - -## Batch Align Objects - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length < 2) -{ - return "Select at least 2 objects"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Align Objects"); - -float startX = selected[0].transform.position.x; -float spacing = 2f; - -for (int i = 0; i < selected.Length; i++) -{ - Undo.RecordObject(selected[i].transform, ""); - Vector3 pos = selected[i].transform.position; - pos.x = startX + (i * spacing); - selected[i].transform.position = pos; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Aligned {selected.Length} objects with {spacing}m spacing"; -``` - -## Batch Rename Assets (Undo-supported) - -```csharp -using UnityEditor; - -// ObjectNames.SetNameSmart() supports Undo (AssetDatabase.RenameAsset() does NOT) -Object[] selected = Selection.objects; -if (selected.Length == 0) -{ - return "No assets selected"; -} - -for (int i = 0; i < selected.Length; i++) -{ - string newName = $"{i:D3}_{selected[i].name}"; - ObjectNames.SetNameSmart(selected[i], newName); -} - -AssetDatabase.SaveAssets(); -return $"Renamed {selected.Length} assets"; -``` - -## Batch Replace Material - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -string materialPath = "Assets/Materials/NewMaterial.mat"; -Material newMat = AssetDatabase.LoadAssetAtPath(materialPath); -if (newMat == null) -{ - return $"Material not found at {materialPath}"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Replace Material"); - -int replaced = 0; -foreach (GameObject obj in selected) -{ - MeshRenderer renderer = obj.GetComponent(); - if (renderer != null) - { - Undo.RecordObject(renderer, ""); - renderer.sharedMaterial = newMat; - replaced++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Replaced material on {replaced} objects"; -``` diff --git a/.codex/skills/uloop-execute-dynamic-code/references/cleanup-operations.md b/.codex/skills/uloop-execute-dynamic-code/references/cleanup-operations.md deleted file mode 100644 index 40eec9a52f..0000000000 --- a/.codex/skills/uloop-execute-dynamic-code/references/cleanup-operations.md +++ /dev/null @@ -1,403 +0,0 @@ -# Cleanup Operations - -Code examples for project cleanup operations using `execute-dynamic-code`. - -## Detect Missing Scripts on GameObject - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -int missingCount = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(selected); -return $"{selected.name} has {missingCount} missing script(s)"; -``` - -## Remove Missing Scripts from GameObject - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -int removedCount = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(selected); -return $"Removed {removedCount} missing script(s) from {selected.name}"; -``` - -## Scan Scene for Missing Scripts - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List objectsWithMissing = new List(); - -foreach (GameObject obj in allObjects) -{ - int count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(obj); - if (count > 0) - { - objectsWithMissing.Add($"{obj.name} ({count})"); - } -} - -if (objectsWithMissing.Count == 0) -{ - return "No missing scripts found in scene"; -} - -return $"Objects with missing scripts: {string.Join(", ", objectsWithMissing)}"; -``` - -## Remove All Missing Scripts from Scene - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -int totalRemoved = 0; - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Remove All Missing Scripts"); - -foreach (GameObject obj in allObjects) -{ - int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(obj); - totalRemoved += removed; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Removed {totalRemoved} missing scripts from scene"; -``` - -## Detect Missing References in Component - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -List missingRefs = new List(); - -Component[] components = selected.GetComponents(); -foreach (Component comp in components) -{ - if (comp == null) continue; - - SerializedObject so = new SerializedObject(comp); - SerializedProperty prop = so.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - missingRefs.Add($"{comp.GetType().Name}.{prop.name}"); - } - } - } -} - -if (missingRefs.Count == 0) -{ - return "No missing references found"; -} - -return $"Missing references: {string.Join(", ", missingRefs)}"; -``` - -## Scan Scene for Missing References - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List results = new List(); - -foreach (GameObject obj in allObjects) -{ - Component[] components = obj.GetComponents(); - foreach (Component comp in components) - { - if (comp == null) continue; - - SerializedObject so = new SerializedObject(comp); - SerializedProperty prop = so.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - results.Add($"{obj.name}/{comp.GetType().Name}.{prop.name}"); - } - } - } - } -} - -if (results.Count == 0) -{ - return "No missing references found in scene"; -} - -return $"Missing references ({results.Count}): {string.Join(", ", results.Take(10))}..."; -``` - -## Find Unused Materials in Project - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string[] materialGuids = AssetDatabase.FindAssets("t:Material"); -HashSet usedMaterials = new HashSet(); - -string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab"); -foreach (string guid in prefabGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - string[] deps = AssetDatabase.GetDependencies(path, true); - foreach (string dep in deps) - { - if (dep.EndsWith(".mat")) - { - usedMaterials.Add(dep); - } - } -} - -// This scan only checks prefab dependencies. Verify scene and other asset -// references manually before deleting any reported materials. -List unusedMaterials = new List(); -foreach (string guid in materialGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - if (!usedMaterials.Contains(path)) - { - unusedMaterials.Add(path); - } -} - -return $"Found {unusedMaterials.Count} materials not referenced by prefabs. Verify scene and other asset references manually before deleting."; -``` - -## Find Empty GameObjects - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List emptyObjects = new List(); - -foreach (GameObject obj in allObjects) -{ - Component[] components = obj.GetComponents(); - if (components.Length == 1 && obj.transform.childCount == 0) - { - emptyObjects.Add(obj.name); - } -} - -if (emptyObjects.Count == 0) -{ - return "No empty GameObjects found"; -} - -return $"Empty objects ({emptyObjects.Count}): {string.Join(", ", emptyObjects.Take(20))}"; -``` - -## Find Duplicate Names in Hierarchy - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -Dictionary nameCounts = new Dictionary(); - -foreach (GameObject obj in allObjects) -{ - if (nameCounts.ContainsKey(obj.name)) - { - nameCounts[obj.name]++; - } - else - { - nameCounts[obj.name] = 1; - } -} - -List duplicates = new List(); -foreach (KeyValuePair kvp in nameCounts) -{ - if (kvp.Value > 1) - { - duplicates.Add($"{kvp.Key} ({kvp.Value})"); - } -} - -if (duplicates.Count == 0) -{ - return "No duplicate names found"; -} - -return $"Duplicate names: {string.Join(", ", duplicates.Take(15))}"; -``` - -## Check for Broken Prefab Instances - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List brokenPrefabs = new List(); - -foreach (GameObject obj in allObjects) -{ - if (PrefabUtility.IsPartOfPrefabInstance(obj)) - { - GameObject prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(obj); - if (prefabAsset == null) - { - brokenPrefabs.Add(obj.name); - } - } -} - -if (brokenPrefabs.Count == 0) -{ - return "No broken prefab instances found"; -} - -return $"Broken prefab instances: {string.Join(", ", brokenPrefabs)}"; -``` - -## Find Objects with Negative Scale - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List negativeScale = new List(); - -foreach (GameObject obj in allObjects) -{ - Vector3 scale = obj.transform.localScale; - if (scale.x < 0 || scale.y < 0 || scale.z < 0) - { - negativeScale.Add($"{obj.name} ({scale})"); - } -} - -if (negativeScale.Count == 0) -{ - return "No objects with negative scale found"; -} - -return $"Negative scale objects: {string.Join(", ", negativeScale.Take(10))}"; -``` - -## Remove Empty Leaf GameObjects - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Remove Empty Leaves"); - -int removedCount = 0; -foreach (GameObject obj in allObjects) -{ - if (obj == null) continue; - - Component[] components = obj.GetComponents(); - if (components.Length == 1 && obj.transform.childCount == 0) - { - Undo.DestroyObjectImmediate(obj); - removedCount++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Removed {removedCount} empty leaf GameObjects"; -``` - -## Find Large Meshes - -```csharp -using UnityEditor; - -string[] meshGuids = AssetDatabase.FindAssets("t:Mesh"); -List largeMeshes = new List(); -int threshold = 10000; - -foreach (string guid in meshGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - Mesh mesh = AssetDatabase.LoadAssetAtPath(path); - if (mesh != null && mesh.vertexCount > threshold) - { - largeMeshes.Add($"{path} ({mesh.vertexCount} verts)"); - } -} - -if (largeMeshes.Count == 0) -{ - return $"No meshes with more than {threshold} vertices found"; -} - -return $"Large meshes: {string.Join(", ", largeMeshes.Take(10))}"; -``` - -## Validate Asset References - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { "Assets/Data" }); -List invalidRefs = new List(); - -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - ScriptableObject so = AssetDatabase.LoadAssetAtPath(path); - if (so == null) continue; - - SerializedObject serializedObj = new SerializedObject(so); - SerializedProperty prop = serializedObj.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - invalidRefs.Add($"{path}: {prop.name}"); - } - } - } -} - -if (invalidRefs.Count == 0) -{ - return "All asset references are valid"; -} - -return $"Invalid references ({invalidRefs.Count}): {string.Join(", ", invalidRefs.Take(10))}"; -``` diff --git a/.codex/skills/uloop-execute-dynamic-code/references/material-operations.md b/.codex/skills/uloop-execute-dynamic-code/references/material-operations.md deleted file mode 100644 index 4defb5c2fe..0000000000 --- a/.codex/skills/uloop-execute-dynamic-code/references/material-operations.md +++ /dev/null @@ -1,172 +0,0 @@ -# Material Operations - -Code examples for Material operations using `execute-dynamic-code`. - -## Create a New Material - -```csharp -using UnityEditor; - -Shader shader = Shader.Find("Standard"); -Material mat = new Material(shader); -mat.name = "MyMaterial"; -string path = "Assets/Materials/MyMaterial.mat"; -AssetDatabase.CreateAsset(mat, path); -AssetDatabase.SaveAssets(); -return $"Material created at {path}"; -``` - -## Set Material Color - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -mat.SetColor("_Color", new Color(1f, 0.5f, 0f, 1f)); -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return "Material color set to orange"; -``` - -## Set Material Properties (Float, Vector) - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -mat.SetFloat("_Metallic", 0.8f); -mat.SetFloat("_Glossiness", 0.6f); -mat.SetVector("_EmissionColor", new Vector4(1, 1, 0, 1)); - -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return "Material properties updated"; -``` - -## Assign Texture to Material - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -string texPath = "Assets/Textures/MyTexture.png"; - -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -Texture2D tex = AssetDatabase.LoadAssetAtPath(texPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} -if (tex == null) -{ - return $"Texture not found at {texPath}"; -} - -mat.SetTexture("_MainTex", tex); -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return $"Assigned {tex.name} to material"; -``` - -## Assign Material to GameObject - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -Renderer renderer = selected.GetComponent(); -if (renderer == null) -{ - return "Selected object has no Renderer"; -} - -renderer.sharedMaterial = mat; -EditorUtility.SetDirty(selected); -return $"Assigned {mat.name} to {selected.name}"; -``` - -## Enable/Disable Material Keywords - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -mat.EnableKeyword("_EMISSION"); -mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; - -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return "Emission enabled on material"; -``` - -## Find All Materials Using a Shader - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string shaderName = "Standard"; -string[] guids = AssetDatabase.FindAssets("t:Material"); -List matchingMaterials = new List(); - -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - Material mat = AssetDatabase.LoadAssetAtPath(path); - if (mat != null && mat.shader != null && mat.shader.name == shaderName) - { - matchingMaterials.Add(path); - } -} -return $"Found {matchingMaterials.Count} materials using {shaderName}"; -``` - -## Duplicate Material - -```csharp -using UnityEditor; - -string sourcePath = "Assets/Materials/MyMaterial.mat"; -string destPath = "Assets/Materials/MyMaterial_Copy.mat"; - -Material source = AssetDatabase.LoadAssetAtPath(sourcePath); -if (source == null) -{ - return $"Material not found at {sourcePath}"; -} - -Material copy = new Material(source); -AssetDatabase.CreateAsset(copy, destPath); -AssetDatabase.SaveAssets(); -return $"Material duplicated to {destPath}"; -``` diff --git a/.codex/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.codex/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md deleted file mode 100644 index aeab072a4b..0000000000 --- a/.codex/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ /dev/null @@ -1,342 +0,0 @@ -# PlayMode Automation (PowerShell) - -Code examples for runtime automation during Play mode using `execute-dynamic-code`. -These examples manipulate live scene objects while the game is running. -Shell command examples in this file target `PowerShell`. - -## When to use dedicated mouse simulation tools instead - -The examples in this file call UI handlers or runtime methods from C#. -That remains the better choice when you want targeted automation, direct state control, or a quick diagnostic path. -Use dedicated mouse tools only when the input route itself is part of what you need to verify. - -Choose the tool based on what you are trying to validate: - -| Scenario | Recommended tool | Why | -|----------|------------------|-----| -| Verify that a uGUI element responds through the real EventSystem pointer path | `simulate-mouse-ui` | Fires `PointerDown` / `PointerUp` / `PointerClick` / drag events through EventSystem raycasts instead of bypassing the UI input route. | -| Test gameplay that reads `Mouse.current`, button state, delta, or scroll | `simulate-mouse-input` | Injects Input System mouse state into `Mouse.current`, so game code can observe `wasPressedThisFrame`, movement delta, and scroll like player input. This assumes the project uses the New Input System (`Input System Package (New)` or `Both`). If that is not available in the target project, prefer `execute-dynamic-code` for a project-specific workaround instead of changing project settings just to use this tool. | -| Jump straight to a known button callback, invoke a method, inspect state, or set up a test precondition | `execute-dynamic-code` | Best when you intentionally want direct automation without reproducing the full input pipeline. | -| Drive custom runtime behavior that does not map cleanly to the built-in mouse tools | `execute-dynamic-code` | Lets you call project-specific methods, inspect scene objects, and prototype one-off flows immediately. | - -In short: do not default everything to mouse simulation. -Use `execute-dynamic-code` for direct automation and diagnostics, and switch to `simulate-mouse-ui` or `simulate-mouse-input` when reproducing the real input path is the thing you need to test. - -## PowerShell 7 Multiline Snippets - -Default to a single-quoted here-string variable in PowerShell 7 (`pwsh`), then pass it to `--code`. - -```powershell -$code = @' -using UnityEngine; -using UnityEngine.SceneManagement; - -Scene scene = SceneManager.GetActiveScene(); -GameObject[] objects = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); -return $"{scene.name}: {objects.Length} objects"; -'@ - -uloop execute-dynamic-code --code $code -``` - -### With parameters - -Pass a JSON object literal, not a JSON string value. Use a second here-string when the parameters are easier to read on separate lines. - -```powershell -$code = @' -return parameters["param0"]; -'@ - -$parameters = @' -{"param0":"Hello from PowerShell"} -'@ - -uloop execute-dynamic-code --code $code --parameters $parameters -``` - -## Short Inline Snippets - -PowerShell 7 (`pwsh`) preserves C# double quotes inside single-quoted native command arguments. - -```powershell -uloop execute-dynamic-code --code 'return "Hello from PowerShell";' -``` - -If the C# snippet itself contains a single quote, double it inside the PowerShell single-quoted string. - -```powershell -uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' -``` - -Wrap JSON object literals for `--parameters` in single quotes so PowerShell passes inner double quotes unchanged. - -```powershell -uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' -``` - -## Windows PowerShell 5.1 - -Windows PowerShell 5.1 removes unescaped double quotes when invoking native commands. Escape C# double quotes as `\"` in short inline snippets. - -```powershell -uloop execute-dynamic-code --code 'return \"Hello from Windows PowerShell\";' -``` - -For multiline or string-heavy snippets, prefer `--code-file`. - -## Native Launcher Check - -Multiline `--code` expects the native Go `uloop.exe`. If `Get-Command uloop` resolves to an old `.cmd` shim, run `uloop install` and open a new terminal. - -```powershell -(Get-Command uloop).Source -``` - -## Click UI Button by Path - -```csharp -using UnityEngine.UI; - -Button btn = GameObject.Find("Canvas/StartButton")?.GetComponent