diff --git a/.agents/skills/ct-ai-dlc/SKILL.md b/.agents/skills/ct-ai-dlc/SKILL.md new file mode 100644 index 0000000..a783da7 --- /dev/null +++ b/.agents/skills/ct-ai-dlc/SKILL.md @@ -0,0 +1,205 @@ +--- +name: ct-ai-dlc +description: "SIRIUS の AI 駆動開発フロー (AI-DLC) の起点スキル。ユーザーが $ct-ai-dlc と明示的に呼び出した時のみ起動し、Intent → Inception → Construction の各 Phase を厳密に進行する。中間成果物は docs/ai-dlc/〈date〉-〈topic-slug〉/ に蓄積される。" +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + + +# SIRIUS AI 駆動開発フロー (AI-DLC) 起点スキル + +このスキルは SIRIUS リポジトリで **AI 駆動開発フロー** を開始/継続するためのもの。 +ユーザーが `$ct-ai-dlc` と明示的に呼び出したときのみ起動する(`agents/openai.yaml` の `allow_implicit_invocation: false` 指定)。 +普段の開発スタイルに影響を与えず、AI-DLC を選択したユーザー・タスクにだけ適用される。 + +## 思想 + +このフローは AWS が提唱する **AI-DLC (AI-Driven Development Life Cycle)** をベースにしている。 +従来の「人間が書く → AI が補助」を反転させ、**AI が能動的に計画・提案 → 人間が承認 → AI が実装** という主従逆転を行う。 + +| 観点 | 従来 | AI-DLC | +|---|---|---| +| AI の役割 | 補助 | 能動的協力者 | +| 人間の役割 | 書く・レビュー | 判断・承認 | +| プロセス | 固定 | 意図に応じて適応 | + +このスキルは厳密にこのフローを進行させるためにある。**自走で判断できる部分は決め打ちで進む**。ユーザーが進行を中断したい場合は会話で介入できる前提なので、ユーザーへの質問 は「人間にしか判断できない場面」に限定して使う。 + +## フロー全体図 + +``` +Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 +Intent 起票 → Inception → Construction → Review → Operations +(何を/なぜ) (どう作る) (実装) (レビュー) (リリース) + ↓ ↓ ↓ ↓ ↓ +intent.md plan.md コード+PR GitHub 検証レポート + └─────────────────┴──── docs/ai-dlc/-/ ────┘ +``` + +各 Phase の成果物は Markdown ファイルとして `docs/ai-dlc/-/` に蓄積される。 +Phase 間の情報引き継ぎは **すべてファイル経由**。これにより各 Phase が自己完結し、Phase 完了後にコンテキストをリセットしても次 Phase が再現可能になる。 + +Phase 4 (Review) は、GitHub の PR にレビュアーを割り当て、チームで使っている連絡手段でレビュー依頼を送る。 +依頼の送り方はチームごとに異なるため、このスキルでは手順を固定していない。 + +## 引数の解釈 + +引数は **自由テキスト** として受け取り、以下を文脈から推測する: + +1. **トピック** — 何の機能・改善か。例: "Impact Frames", "ハイブリッドGI", "Smear Pass 改善" +2. **開始フェーズ** — どの Phase から始めるか。明示キーワードがあれば採用、なければ既存状況から推定 + +### フェーズキーワード(参考、網羅でなくてよい) + +| キーワード例 | フェーズ | +|---|---| +| `intent`, `起票`, `新規`, `提案` | Phase 1 (Intent) | +| `inception`, `計画`, `設計`, `plan` | Phase 2 (Inception) | +| `construction`, `実装`, `construct`, `作る` | Phase 3 (Construction) | + +明示キーワードがなければ、既存フォルダの中身から判定する: +- フォルダなし → Phase 1 (新規 Intent) +- `intent.md` のみ → Phase 2 が次 +- `intent.md` + `plan.md` → Phase 3 が次 + +### 引数解釈の動作例 + +| 入力 | 解釈 | 動作 | +|---|---|---| +| `$ct-ai-dlc` | 引数なし | `docs/ai-dlc/` を ls して進行中のフォルダを列挙、選択肢を提示 | +| `$ct-ai-dlc impactframe実装` | トピック=impactframe実装 | フォルダ検索 → なければ新規 Phase 1 | +| `$ct-ai-dlc impactframe実装をinceptionから` | トピック+フェーズ=Phase 2 | 既存フォルダを Phase 2 から開始 | +| `$ct-ai-dlc ハイブリッドGIの計画立てる` | トピック+「計画」=Phase 2 | 既存フォルダ検索 → Phase 2 | +| `$ct-ai-dlc 続きから` | 直近の進行中フォルダ | 進行中フォルダの中身から次フェーズを判定 | +| `$ct-ai-dlc <チャット/ドキュメントの URL>` 等 | URL を含む | 該当ツールで内容を取得 → トピック推定 → 新規 Phase 1 | + +### 外部情報源 URL が含まれる場合 + +引数や対話の中で **チャットのスレッド / ドキュメントツールのページ / GitHub Issue / PR** などの URL が渡された場合は、対応するツール(各サービスの MCP ツール、`gh api` 等)で内容を取得し、Intent の Description / Context の素材として利用する。 + +引き出す情報の典型例: +- 機能のトピック(フォルダ slug 推定の材料) +- 依頼の背景・経緯(Context の「背景となる依頼」に閉じる) +- 既存代替手段の有無・チーム内合意状況 +- 利用想定(誰がどう使うか) + +スケジュール情報がそこに書かれていても Intent には転記しない(Phase 1 reference の「Intent に書かないもの」参照)。スケジュールは GitHub Issue / PR description / カンバン側で管理する。 + +## 処理フロー(このスキルが実行する手順) + +### Step 1: 引数の解釈 + +引数からトピックとフェーズを推測する。複数候補や曖昧さがある場合のみ ユーザーへの質問 を使う。 +**単一候補や妥当な推測ができる場合は決め打ちで進む**。ユーザーが意図と違えば会話で訂正する。 + +### Step 2: 既存フォルダの検索 + +```bash +ls docs/ai-dlc/ +``` + +トピックキーワードでフォルダ名をファジーマッチ。フォルダ名形式は `-`。 + +- 候補 0 件 → 新規 Phase 1 を開始(フォルダはこのスキルが作成する) +- 候補 1 件 → 採用、次ステップへ +- 候補 2 件以上 → ユーザーへの質問 で 1 つ選択 + +### Step 3: フォルダの作成 / 検証 + +**新規の場合:** +- 日付プレフィックス: 今日の日付を `YYYY-MM-DD` で取得(`date -u +%Y-%m-%d`) +- トピック slug: 日本語入力を kebab-case 英数字に正規化(例: `impactframe実装` → `impactframe-impl`、`ハイブリッドGI` → `hybrid-gi`) + - **課題ベースで命名する**: 実装手段ではなく解決したい課題で命名する。 + - ✅ `outline-inner-bleed-suppression`(課題 = アウトラインの内側にじみ抑制) + - ❌ `outline-stencil-mask`(実装手段 = ステンシルマスク) + + 理由: Intent は実装方針を固定しないドキュメントなので、フォルダ名も実装手段に寄せると Phase 2 で別案に切り替わった時に陳腐化する。 +- 作成: `mkdir -p docs/ai-dlc/-/` + +正規化に決定的な答えがない場合は、もっとも自然な候補を採用して進む。ユーザーが気に入らなければ会話で訂正する。 + +**既存の場合:** +- `ls docs/ai-dlc/-/` で既存ファイル確認 +- フェーズ整合性チェック(例: Phase 2 を開始しようとしているのに `intent.md` がなければエラー報告) + +### Step 4: フェーズの実行 + +該当フェーズの reference を読み込み、その手順に従って実行する: + +| フェーズ | reference | +|---|---| +| Phase 1 (Intent) | [references/phase-1-intent.md](references/phase-1-intent.md) | +| Phase 2 (Inception) | [references/phase-2-inception.md](references/phase-2-inception.md) | +| Phase 3 (Construction) | [references/phase-3-construction.md](references/phase-3-construction.md) | + +reference の読み込みは「フェーズ実行直前」に行うこと。スキル起動時に全部読むとコンテキストが無駄に膨らむ。 + +### Step 5: フェーズ完了時のメッセージ + +フェーズが完了したら、生成したファイルのパスと **次フェーズの起動コマンド** を提示する。 +コンテキストリセット (新しい会話の開始) を **軽く推奨** するが強制はしない。 + +``` +✅ Phase 1 (Intent) 完了 + 出力: docs/ai-dlc/2026-05-26-impactframe-impl/intent.md + +次は Phase 2 (Inception) です。 +コンテキストをリセットしてから実行すると、Markdown 引き継ぎの自己完結性が担保され +トークン消費も抑えられます(任意)。 + (任意)新しい会話を開始し、次の入力で再開 + $ct-ai-dlc impactframe実装をinceptionから +``` + +次フェーズの引数は **コピペで動く形** にすること(トピック名 + フェーズ指定を含める)。 + +## 共通ルール + +### git 操作はユーザー許可制(MUST) + +`git add` / `commit` / `push`、ブランチ作成、PR 作成(`gh pr create`)など、**リポジトリの状態やリモートを変える操作は、ユーザーが明示的に許可した場合にのみ行う**。許可がない間は、実装・コンパイル・テスト・ドキュメント編集・作業ツリー上の変更までに留める。 + +- 各 Phase は「git 操作の直前」で一旦立ち止まり、何を・どこにコミット / PR 化するかを提示してユーザーの承認を得る +- この方針は下記「Human in the Loop の方針」の *迷ったら進む* および「自走で判断できる部分は決め打ちで進む」より **優先する**(git 操作は自走の対象外) +- コミット先・ブランチ運用・PR 手順の詳細は [AGENTS.md](../../../AGENTS.md) およびユーザーグローバル `~/.codex/AGENTS.md` の git 運用規約に従う + +### 触ってはいけないファイル + +- `.meta` ファイル — Unity Editor が生成する。AI が生成した GUID は既存と衝突する可能性がある(PR #553 の教訓) +- サブモジュールポインタ — SiriusPackages / SiriusAssets の HEAD 変更は SIRIUS にコミットしない +- `Packages/manifest.json` の tarball swap 後の状態 — 一時改変のみ、コミットしない +- `LocalPackages/*.tgz` — 検証用、Git 管理外 + +### コミット先の判断 + +実装対象がどのリポジトリに着地するかは [references/sirius-repos.md](references/sirius-repos.md) を参照。 +このスキルは **Phase 3 (Construction) で実装に入る直前** にこの reference を読み込む。 + +### 品質ゲート(PR マージ前 — MUST) + +PR をマージする前に、**macOS(iOS ターゲット)と Windows(Android ターゲット)の両方で全 AverageTest(`Assets/Tests/Runtime/AverageTest.cs` の PlayMode ビジュアルリグレッション全ケース + EditMode 全テスト)が成功していること**を必須とする。`AverageTest` は実行 OS の期待ターゲット以外だとスキップされるため、**両プラットフォームを別々に担保する**(macOS→iOS / Windows→Android、WebGL は共通)。CI に Unity テストの自動ゲートが無いため、**AI が手動で TestRunner を全件実行**して担保する。閾値近傍で初回失敗するテストは、失敗テストのみを対象に**初回 + リトライ 2 回 = 最大 3 試行**まで実行し、**1 回でも成功すれば PASS** として扱う。**3 試行連続で失敗したテストは「確定失敗」とし、それ以上リトライせず、FLIP Mean / 閾値 / 差分画像パスを添えて ユーザーへの質問 で人間の判断に引き渡す**(flaky か実装起因かの最終判定は人間が行う。実装起因と判断された場合のみ修正して再度全件を成功させる)。具体手順・CLI タイムアウト時の判定方法は [Phase 3 Step 4](references/phase-3-construction.md) を参照。 + +### Markdown 引き継ぎ規約 + +- すべての中間成果物は `docs/ai-dlc/-/` に置く +- ファイル名は **artifact 名で固定**: `intent.md` / `plan.md` +- 日付やトピックはフォルダ名に持たせる(ファイル名には持たせない) +- 次フェーズで読むファイルがすべて揃った状態でフェーズ完了とする + +### Human in the Loop の方針 + +AI-DLC の核は「重要な判断には人間確認を必須」だが、SIRIUS では **過度の確認はフローを停滞させる** ため次のように運用する: + +- ✅ ユーザーへの質問 を使う: 真に複数の妥当な選択肢がある場面、データ削除など不可逆な操作前 +- ❌ 使わない: 自動推測で妥当な選択ができる場面、補完情報の確認(ユーザーは会話で介入できる) + +迷ったら **進む** ことを優先する。間違っていればユーザーが訂正する。 + +## 関連ドキュメント + +- [docs/ai-dlc/README.md](../../../docs/ai-dlc/README.md) — `docs/ai-dlc/` 配下の運用ルール +- [references/sirius-repos.md](references/sirius-repos.md) — リポジトリ役割マップ +- [assets/intent-template.md](assets/intent-template.md) — Intent 雛形 +- [assets/plan-template.md](assets/plan-template.md) — Plan 雛形 +- [assets/pr-template.md](assets/pr-template.md) — PR 本文雛形(Phase 3 Step 7・lean / 署名なし) diff --git a/.agents/skills/ct-ai-dlc/agents/openai.yaml b/.agents/skills/ct-ai-dlc/agents/openai.yaml new file mode 100644 index 0000000..5b1f887 --- /dev/null +++ b/.agents/skills/ct-ai-dlc/agents/openai.yaml @@ -0,0 +1,2 @@ +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/ct-ai-dlc/assets/intent-template.md b/.agents/skills/ct-ai-dlc/assets/intent-template.md new file mode 100644 index 0000000..8ce1e7b --- /dev/null +++ b/.agents/skills/ct-ai-dlc/assets/intent-template.md @@ -0,0 +1,129 @@ +# <機能名> + + + +## Description + + + +## Context + + + +## Completion Criteria + + + +### ✅ 成功ケース + + + +### ❌ 失敗ケース + + + +### 🔒 品質ゲート + + + + diff --git a/.agents/skills/ct-ai-dlc/assets/plan-template.md b/.agents/skills/ct-ai-dlc/assets/plan-template.md new file mode 100644 index 0000000..31668cd --- /dev/null +++ b/.agents/skills/ct-ai-dlc/assets/plan-template.md @@ -0,0 +1,79 @@ +# <機能名> 実装計画 + + + +## 採用設計 + + + +## 棄却した代替案 + + + +## Units of Work + + + +### UoW#1 [<役割>] <依存・並列の注記> + +- 対象: <パス> +- 追加 / 変更ファイル: <一覧> +- 依存: <他 UoW or なし> +- 担当: AI / 人間 / 両方 +- コミット先: SIRIUS / SiriusPackages / SiriusAssets + +### UoW#2 [...] + +... + +## 並列可能ペア + + + +## 触ってはいけないファイル + + + +## PR 構成 + + diff --git a/.agents/skills/ct-ai-dlc/assets/pr-template.md b/.agents/skills/ct-ai-dlc/assets/pr-template.md new file mode 100644 index 0000000..a530abc --- /dev/null +++ b/.agents/skills/ct-ai-dlc/assets/pr-template.md @@ -0,0 +1,41 @@ + + +## 概要 + + +- <変更の要点> + +## 設計意図 + + +- <採用したアプローチと理由。例: 既存 GBuffer を流用し追加パス・ステンシル増設なし/OFF 時ゼロコスト。詳細は plan.md 参照> + +## テスト + + +- [ ] macOS で iOS の全 AverageTest 成功 +- [ ] Windows で Android の全 AverageTest 成功 +- <その他に行った確認があれば追記。なければ削除> + +## 関連 + + +- Intent / Plan: docs/ai-dlc/-/ +- 関連 PR: <例: #YYY, #XXX> diff --git a/.agents/skills/ct-ai-dlc/references/cheatsheet.md b/.agents/skills/ct-ai-dlc/references/cheatsheet.md new file mode 100644 index 0000000..00a3b93 --- /dev/null +++ b/.agents/skills/ct-ai-dlc/references/cheatsheet.md @@ -0,0 +1,115 @@ +# AI-DLC チートシート(Phase 1〜4) + +各フェーズで **決めるもの / 作るもの(成果物)/ やらない・注意 / 起動コマンド** を1枚に凝縮したもの。 +詳細は各フェーズの reference を参照。Phase 5 (Operations) は未整備のため本チートには含めない。 + +``` +Phase 1 Phase 2 Phase 3 Phase 4 +Intent → Inception → Construction → Review +(何を/なぜ) (どう作る) (実装) (レビュー) + ↓ ↓ ↓ ↓ +intent.md plan.md コード + PR GitHub + └────────────── docs/ai-dlc/-/ ──────────┘ +``` + +Phase 間の引き継ぎは **すべてファイル経由**(自己完結)。フェーズ完了ごとに 新しい会話の開始 してよい。 + +--- + +## 早見表 + +| Phase | 決めるもの(核) | 作るもの | 起動コマンド | +|---|---|---|---| +| **1 Intent** | Description / Context(★動的軸)/ Completion Criteria(SMAV) | `intent.md` | `$ct-ai-dlc <トピック>` | +| **2 Inception** | 既存制約調査 → 採用設計(公開階層)→ UoW 分解 → コミット先 | `plan.md` | `$ct-ai-dlc <トピック>をinceptionから` | +| **3 Construction** | UoW 順次実装 → コンパイル0 → 全 AverageTest 成功(品質ゲート) | **PR** | `$ct-ai-dlc <トピック>を実装` | +| **4 Review** | 対象 PR / レビュアー / 変更概要 | GitHub レビュアー割当 + レビュー依頼 | ―(チームの運用に合わせる) | + +--- + +## Phase 1: Intent(何を / なぜ) +参照: [phase-1-intent.md](phase-1-intent.md) / 雛形: [../assets/intent-template.md](../assets/intent-template.md) + +**決めるもの** +- **Description** — 何を作るか。汎用機能として記述(プロダクト固有用語なし)。実装方針は書かない +- **Context** — なぜ・制約 + - ★**動的軸(静的 / 半動的 / 完全動的)=必須**(Phase 2 のインタフェース層決定の入力。半動的・完全動的なら切替経路も) + - 配置先パッケージ候補 / 既存類似機構との関係 / パフォーマンス制約(相対・定性)/ 利用想定 / 背景依頼 / トピック固有で AGENTS.md にない教訓 +- **Completion Criteria** — SMAV を満たす完了条件を3要素で:✅成功ケース/❌失敗ケース/🔒品質ゲート(検証カテゴリ+客観メトリクス) + +**書かない4種** +スケジュール / 後段で決まる数値プレースホルダ / AGENTS.md 既出ルール / 具体の検証手段の固有名(→ 品質ゲートは「カテゴリ+メトリクス」で書き、手段は Phase 2 plan.md へ) + +**作るもの**: `docs/ai-dlc/-/intent.md`(1枚) + +--- + +## Phase 2: Inception(どう作る) +参照: [phase-2-inception.md](phase-2-inception.md) / 雛形: [../assets/plan-template.md](../assets/plan-template.md) + +**決めるもの** +- **既存実装の制約(Step 1・設計前に必須)** — 変更関数の本体ロジック/1-hop caller・callee/データ書き込み元/エンコード・命名・単位の規則/入力経路・既存公開階層。**既存パターンを1段落で言語化**(以降の採否判断の土台) +- **採用設計(Step 2–3)** — 2〜3案を発散 → 1案採用 + - 評価2軸: ①既存原則との整合 ②新規導入インフラの量 + - 優先順: 既存原則に整合する案を最優先。逸脱案は**客観的理由**が必須 +- **インタフェース層(Step 3)** — intent の動的軸 + 既存公開階層と整合 + - 公開階層(VolumeComponent / MonoBehaviour SerializeField / Material Property / ProjectSettings)+理由 / API パターン(getter・setter・命名・既定値を既存と揃える)/ 配置場所 +- **UoW 分解(Step 4)** — 対象ファイル・依存・並列可否・担当(AI/人間/両方)。粒度 ≒ コミット1〜2個。ShaderGraph・Timeline・FBX 配置は**人間作業として明示** +- **コミット先(Step 5)** — 変更がどのディレクトリに着地するか(`SiriusPackages/` のパッケージ実体/`Assets/` のデモ・テスト/`docs/` など) + +**残す・守る**: 棄却案と**客観的な棄却理由**を消さない(「なぜこの設計か」の記録) + +**作るもの**: `docs/ai-dlc/-/plan.md`(採用設計+公開階層/棄却案と理由/UoW一覧〈対象・依存・担当・コミット先〉/並列可能ペア/触ってはいけないファイル) + +--- + +## Phase 3: Construction(実装) +参照: [phase-3-construction.md](phase-3-construction.md) / PR 雛形: [../assets/pr-template.md](../assets/pr-template.md) + +**やること / 決めるもの** +- **実装(Step 2)** — UoW を1件ずつ順次(本構成では並列実装しない)。対象パッケージの AGENTS.md / SKILL.md を読み、Read → Edit/Write。判断点は ユーザーへの質問 で止めず地の文で報告 +- **コンパイル(Step 3)** — エラーが消えるまで次に進まない +- **テスト=品質ゲート(MUST・Step 4)** + - **macOS(iOS)と Windows(Android)両方**で全 AverageTest(PlayMode ビジュアルリグレッション全ケース+EditMode 全件)成功 + - シェーダ・描画変更は他シーンに波及 → **変更箇所に関係なく必ず全件** + - **flaky 規則**: 初回+リトライ2回=最大3試行、リトライは失敗テストのみ、1回でも成功で PASS。**3連続失敗=確定失敗 → FLIP Mean/閾値/差分画像を添えて ユーザーへの質問 で人間判断** + - CLI タイムアウト(180秒)≠ テスト失敗 → `run_in_background` + `TestResults/.xml` で判定 +- **ビジュアル検証(Step 4.5)** — ビルドターゲット切替(macOS→iOS / Windows→Android)/git stash で OFF ベースライン比較/ON は `Time.timeScale=0` で screenshot/execute-dynamic-code の落とし穴(`sharedProfile` 変更・`TryGet`・完全修飾) + +**守るルール(git 操作以降)** +- **commit / push / PR はユーザー明示許可制**。許可がなければ Step 4 完了(実装+テスト通過)で報告して停止 +- コミットメッセージは日本語 / `git add` はファイル名明示 / `.meta` は AI 編集分を混入させない +- **plan.md と実装が食い違ったら plan.md を更新** + +**作るもの**: **PR**(pr-template、lean・署名なし) +- `SiriusPackages/` のパッケージ実体が変わったら、該当パッケージの AGENTS.md / SKILL.md を**別コミット**で更新 + +--- + +## Phase 4: Review(レビュー) +専用 reference なし。レビュー依頼の送り方はチームの運用に合わせる。 + +**決めるもの** +- 対象 PR(引数 or 現在ブランチから自動検出、関連 PR を束ねる) +- レビュアー選択(PR author を除くメンバーから複数選択・表示名で提示) +- 変更概要(**1〜2個の箇条書き**、詳細は PR 本文に委ねる) +- 送信可否(プレビュー → 送信/編集/キャンセル) + +**やること** +- GitHub レビュアー設定(`gh pr edit --add-reviewer ...`、関連 PR にも同じレビュアー) +- チームで使っている連絡手段でレビュー依頼を送る + +**注意** +- **送信前に必ずプレビュー確認**(外部発信) +- 依頼文はプレーンテキスト中心にする(チャットツールによってはマークダウン記法がリンク解析と干渉する) + +**作るもの**: レビュー依頼 + GitHub 側レビュアー割当 + +--- + +## 全フェーズ共通ルール +- **git 操作はユーザー許可制**(add / commit / push / ブランチ作成 / PR 作成)。自走判断より優先 +- **触ってはいけないファイル**: `.meta`(Unity 生成)/`Packages/manifest.json` の swap 状態/`LocalPackages/*.tgz` +- **品質ゲート(PR マージ前)**: macOS→iOS と Windows→Android の両方で全 AverageTest 成功 +- **Markdown 引き継ぎ**: 中間成果物は `docs/ai-dlc/-/` に固定ファイル名(`intent.md` / `plan.md`)で置く +- **Human in the Loop**: 迷ったら進む。ユーザーへの質問 は「人間にしか判断できない場面/不可逆操作の前」に限定 diff --git a/.agents/skills/ct-ai-dlc/references/phase-1-intent.md b/.agents/skills/ct-ai-dlc/references/phase-1-intent.md new file mode 100644 index 0000000..96c96ce --- /dev/null +++ b/.agents/skills/ct-ai-dlc/references/phase-1-intent.md @@ -0,0 +1,160 @@ +# Phase 1: Intent 起票 + +このフェーズでは、「何を・なぜ作るか」を Intent として言語化する。 +Intent は AI-DLC のすべての起点で、後続フェーズの判断基準になる。 + +## 成果物 + +`docs/ai-dlc/-/intent.md` (テンプレ: [../assets/intent-template.md](../assets/intent-template.md)) + +## Intent の 3 要素 + +| 要素 | 内容 | 例 | +|---|---|---| +| **Description** | 何を作るか | 「攻撃ヒット時に画面を強調する Impact Frames 機能を追加」 | +| **Context** | なぜ・どんな制約か | 「Cross Punch アニメと同期する演出。モバイル GPU 予算 1ms 以内」 | +| **Completion Criteria** | 完了条件 | 「Timeline Clip で配置可、複数 Clip 重疊で破綻しない、`uloop-run-tests` 通過」 | + +## Completion Criteria は SMAV を満たすほど AI 自律性が上がる + +| 属性 | 意味 | 不十分な例 | SMAV な例 | +|---|---|---|---| +| **S**pecific | 具体的 | 「速くする」 | 「p95 で 200ms 以内」 | +| **M**easurable | 測定可能 | 「使いやすく」 | 「DL 開始まで 3 秒」 | +| **A**tomic | 原子的 | 「全エッジケース」 | 「id 不在で 404」 | +| **V**erifiable | 検証可能 | 「コードがきれい」 | 「ESLint エラー 0」 | + +理由: 完了条件が SMAV であるほど、AI は後段で「迷わずに」実装判断できる。曖昧な基準は Phase 2/3 で何度も人間に確認することになり、フローが停滞する。 + +## Completion Criteria は 3 要素で書く + +- ✅ **成功ケース** — 達成すべき動作 +- ❌ **失敗ケース** — 起きてはいけないこと(エラーハンドリング含む) +- 🔒 **品質ゲート** — 客観的な合格基準(コンパイル、テスト、レビュー) + +この 3 つを書くと、AI はエラーハンドリングまで自律的に実装する。 + +## Intent に書かないもの(4 種) + +Intent は「何を / なぜ」の安定した核を書くドキュメント(揺れやすい運用情報は持たせない)。次の 4 種は混入しやすいが、いずれも入れない: + +| カテゴリ | 例 | 代わりにどうするか | +|---|---|---| +| **スケジュール** | リリース日 / マイルストーン / 期限 | GitHub Issue / PR description / カンバンで管理 | +| **後段で決まる数値プレースホルダ** | 「GPU 予算 X ms は Phase 2 で plan.md に転記」 | 相対・定性的な基準(「OFF 時 0」「対象オブジェクト数のオーダー以内」等)に留める | +| **AGENTS.md 既出ルール** | `.meta` AI 編集禁止、サブモジュールポインタ非コミット、`Packages/manifest.json` swap 状態非コミット、領域別ガイドライン参照、コミットメッセージは日本語、など | AGENTS.md が常時ロードされるため、Intent への重複記載は DRY 違反。Intent に書くのは **そのトピック固有で AGENTS.md にない教訓** だけ | +| **具体の検証手段の固有名** | `uloop-compile`、`uloop-run-tests`、Unity Frame Debugger、Profiler など | Intent の品質ゲートは「検証カテゴリ + 客観メトリクス」で書き、「具体の検証手段は Phase 2 で plan.md に記述」と末尾に明記する | + +**判定基準**: Intent に残すのは次の 4 条件をすべて満たすもののみ: + +1. 後段 AI(Phase 2/3)の判断材料になる +2. 変動しない(少なくとも本トピック完了まで動かない) +3. 他のドキュメント/システムで管理されていない +4. 手段非依存(特定ツール/スキル/コマンド名に依存しない) + +## Description / Completion Criteria は汎用的に書く + +SIRIUS は複数プロダクト共通基盤なので、**Description と Completion Criteria では特定プロダクト固有の用語を使わない**。プロダクト固有の文脈(依頼元名、ゲーム内固有名詞、特定シーン名など)は **Context** の「背景となる依頼」「利用想定」に閉じる。 + +| セクション | プロダクト固有用語 | 例 | +|---|---|---| +| Description | ❌ 使わない | 「Gift で配置される Collectible のアウトライン...」 → 「SIRIUS のアウトライン機構に対し...」 | +| Completion Criteria | ❌ 使わない | 「Gift マッチフィクトリーと同等の見た目」 → 「対象モデル外側のみアウトライン描画」 | +| Context | ✅ OK | 「背景となる依頼: 一部プロダクト演出で...の要望が起点」 | + +**理由**: Description / Completion Criteria が特定プロダクトに紐づくと、機能の汎用性(他プロダクトでの再利用可能性)が見えづらくなり、Phase 2 で「他プロダクトにも汎用化するか」の判断が偏る。 + +## 品質ゲートの書き方 + +検証カテゴリ + 客観メトリクスで書き、固有の手段名は plan.md に回す。 + +| レベル | 例 | Intent に書く? | +|---|---|---| +| **検証カテゴリ**(何を担保するか) | コンパイルエラー 0、自動テスト pass、VRT 差分なし | ✅ 書く | +| **客観メトリクス**(業界標準語彙) | 警告 0、追加ドローコール 0、ステンシルバッファ汚染なし | ✅ 書く | +| **検証手段の固有名**(具体ツール / スキル / コマンド) | `uloop-compile`、`uloop-run-tests`、Unity Frame Debugger | ❌ Phase 2 plan.md で書く | + +末尾に **「具体の検証手段(コマンド / スキル / 計測ツール)は Phase 2 (Inception) で plan.md に記述する。」** と明示すること。 + +## 動的軸を必ず確認する + +Context に書く項目のうち、**動的軸** は Phase 2 でインタフェース層を決める入力になるため、Phase 1 で確定させる必要がある。intent.md に書かれていないと、Phase 2 で AI が「これは静的に設定する想定ですか?それともランタイムで切り替わりますか?」と確認しに戻り、フローが停滞する。 + +| 動的軸 | 定義 | Phase 2 で影響する論点 | +|---|---|---| +| **静的** | Inspector / Volume Profile で事前設定、ランタイム変更なし | OFF 時 cost 0 担保が緩い基準でよい | +| **半動的** | シーン入退出やステート遷移で C# が切替(数秒〜数分単位) | 切替時のちらつき・リーク・整合性要件が完了条件に入る | +| **完全動的** | フレーム単位 / 入力単位で頻繁に切替 | per-frame の追加コストを厳密に評価する必要 | + +ユーザーに尋ねる時の例: +> この機能は次のどれで利用されますか? +> - 静的(Inspector / Volume Profile で事前設定) +> - 半動的(シーン入退出やステート遷移で C# が切替) +> - 完全動的(フレーム単位で頻繁に切替) +> +> 半動的・完全動的の場合、どのスクリプト/イベントが切り替えを行う想定か教えてください。 + +intent.md の Context 「動的軸」項目にこの答えを反映する。Phase 2 の `phase-2-inception.md` Step 1 (深掘り調査) と Step 3 (公開階層選定) でこの情報が参照される。 + +## 手順 + +### Step 1: 対話で 3 要素を引き出す + +ユーザーとの対話で **Description / Context / Completion Criteria** を埋める。 +質問は **不足している要素に絞る**。すでに引数や会話から推測できることは再確認しない。 + +ただし **動的軸** は intent.md に必須項目なので、明示的に確認すること(推測で埋めないこと。動的軸の前提ズレは Phase 2 全体のやり直しに繋がりやすい)。 + +例(不足要素のみ尋ねる): +> 引数から「攻撃ヒット時のインパクトフレーム機能」と理解しました。 +> 以下が不明なので教えてください: +> - 対象パッケージ: Sirius.PostProcessing で合っていますか? +> - GPU 予算の目安はありますか? +> - 動的軸: 静的 / 半動的 / 完全動的のどれですか?切替経路は? +> - 完了条件として「これだけは満たしたい」というテスト/動作はありますか? + +### Step 2: テンプレートをコピーして埋める + +```bash +cp .agents/skills/ct-ai-dlc/assets/intent-template.md \ + docs/ai-dlc/-/intent.md +``` + +`-` はスキル起動時に決定済みのフォルダ名を使う。 + +### Step 3: SMAV チェック + +書き終えた intent.md を読み返し、Completion Criteria の各項目が SMAV を満たすか自己点検する。 +満たさないものは具体化する。曖昧な基準を残したまま Phase 2 に進むと後で必ず詰まる。 + +### Step 4: 内容の最終確認 + +ユーザーに intent.md の中身を提示して合意を取る。 +**全文を貼り付ける必要はなく**、要約 + 「全文は `docs/ai-dlc/.../intent.md` を参照」で十分。 + +ユーザーが修正を入れたければ会話で指示するか、ファイルを直接編集する。 + +### Step 5: 完了メッセージ + +``` +✅ Phase 1 (Intent) 完了 + 出力: docs/ai-dlc/-/intent.md + +次は Phase 2 (Inception) です。 +コンテキストをリセットしてから実行することを推奨します(任意): + (任意)新しい会話を開始し、次の入力で再開 + $ct-ai-dlc <トピック>をinceptionから +``` + +`<トピック>` は元の引数のトピック部分を使う。コピペで動く形にする。 + +## このフェーズで ユーザーへの質問 を使う場面 + +- ✅ Description / Context / Completion Criteria の不足要素を質問する時 +- ❌ 「Phase 2 に進んでいいですか?」のような形式的確認(メッセージで誘導するだけで十分) + +## 注意事項 + +- intent.md には実装方針や UoW 分解を **書かない**。それは Phase 2 (Inception) の仕事 +- intent.md は「**何を / なぜ**」の安定した核を書く。Phase 2/3 で要件が大きく変わった場合は intent.md を更新し、その変更点からフローを再進行する(別フォルダは作らない) +- 過去 PR の参照リンクを Context に書くのは「**そのトピック固有で AGENTS.md にない教訓**」に限定する。AGENTS.md 既出ルール(`.meta` 編集禁止 / サブモジュール / Packages/manifest.json 等)を Intent に再記載しない diff --git a/.agents/skills/ct-ai-dlc/references/phase-2-inception.md b/.agents/skills/ct-ai-dlc/references/phase-2-inception.md new file mode 100644 index 0000000..60a9b98 --- /dev/null +++ b/.agents/skills/ct-ai-dlc/references/phase-2-inception.md @@ -0,0 +1,161 @@ +# Phase 2: Inception(設計と UoW 分解) + +このフェーズでは、Intent を「どう作るか」に落とす。 +複数の設計案を発散させて1つを選び、実装単位 (UoW: Unit of Work) に分解する。 + +## 成果物 + +`docs/ai-dlc/-/plan.md` (テンプレ: [../assets/plan-template.md](../assets/plan-template.md)) + +## 入力 + +- `docs/ai-dlc/-/intent.md` (Phase 1 の成果物) +- `SiriusPackages//AGENTS.md`, `SKILL.md` (対象パッケージがある場合) +- [./sirius-repos.md](./sirius-repos.md) (リポジトリ役割マップ) + +## 手順 + +### Step 1: 既存実装の深掘り調査(チェックリスト) + +設計案を考える前に必ず以下を完了する。**1 つでも飛ばすと Phase 2 の途中で「実装の制約条件を見落として案を出し直し」になる確率が高い**。本セッションでこの遅延が多発した実例があり、AI には「関連ファイル名を列挙して終わり」になりがちなので、チェックリストとして明示する。 + +``` +□ 直接変更する関数 / クラスの【本体ロジック】を Read(シグネチャだけでなく中身まで) +□ その関数を呼ぶ側 (1-hop caller) を Read +□ その関数が依存する側 (1-hop callee) を Read +□ 関連するデータの【書き込み元】を特定し Read + 例: シェーダで読む uniform があれば、その値を書く C# / 上流 Pass / 別シェーダ + 例: GBuffer チャネルを読むなら、そのチャネルを書く側のシェーダ +□ データのエンコード / 命名 / 単位の規則を 1 文で言語化 + 例:「ObjectID は `(_ObjectID + 1.1) / 64.0` で UNorm エンコード、最大 64 体」 +□ 設定値の【入力経路】を特定(MonoBehaviour SerializeField / VolumeComponent / Material Property / ProjectSettings のどれか) +□ 同種のユーザー設定値が既存にあれば、その【公開階層】を確認(VolumeComponent か MonoBehaviour か等) +□ シェーダ変更を伴う場合: 該当シェーダの `#pragma multi_compile*` の使い分け頻度を grep でカウント +□ 既存パターンを 1 段落で言語化する(次項参照) +``` + +#### 既存パターンを 1 段落で言語化する + +チェックリスト最後の項目が特に重要。「この機能領域の既存コードは X の原則で動いている」を 1〜2 文で書き出す。 + +例: +- 「RenderPass は **Volume 駆動の自己完結原則**。パスは Volume の値だけを見て動き、シーン側の状態を参照しない」 +- 「Sirius.PostProcessing の Volume 系は **Inspector + script 両用パターン**。`Use*` 系プロパティに getter/setter ペアを持つ」 +- 「RotationBlur.shader は **頂点処理が単純で fragment 中心**。Blit 前提で vertex は固定、負荷は fragment のサンプル数で決まる」 + +この言語化があれば、後続の設計案で「既存原則に沿うか / 逸脱するか」が即座に判断できる。 + +調査範囲が広い場合は 調査用エージェントを並列起動して効率化できる(読み取りのみなので安全)。ただし **本 PR ではサブエージェントは使わない方針** のため、メインでの調査で十分なケースが大半。 + +### Step 2: 設計案を 2〜3 案発散(既存パターンとの整合性を必ず評価) + +すぐに 1 案に絞らず、**複数案を発散** させてから比較する。 +案ごとに「採用したらどう実装するか」「どこで詰まるか」を簡潔に書く。 + +例: +- **Plan A**: Volume 直接駆動(Timeline なし) — シンプルだが Timeline 同期の要求と整合しない +- **Plan B**: Timeline Clip + Mixer + Volume — Mixer の blend が複雑だが要求に最も適合 +- **Plan C**: ScriptableObject エフェクトリスト — Mixer 構造を再現する Editor が複雑化 + +#### 各案を 2 軸で評価する + +1. **既存原則との整合**: Step 1 で言語化した「この領域の既存原則」と整合するか / 逸脱するか +2. **新規導入インフラ**: 既存にない仕組み(新規 keyword / 新規 varying / per-instance データ経路 / 新規 RT / 新規 attachment 構成 / 新規ドローコール経路)をどれだけ追加するか + +#### 採用判断の優先順 + +1. **既存原則に整合する案を最優先する**。既存と同じ仕組みで実現できるなら、それを選ぶ +2. 既存原則に逸脱する案を採用する場合、**明示的な理由** が必要 + 例: 性能要件で既存パターンが破綻する、新規ユースケースで既存パターンが構造的に対応できない、等 +3. 「新しい仕組みを足したほうがエレガント / 拡張性がある」は採用理由として **弱い**。既存原則を尊重したほうが、レビュー時間 / 学習コスト / 将来の改修コストが小さい + +「既存原則と異質である」ことは **客観的な棄却理由** として書いてよい(既存設計と異質な案はそれ自体がメンテナンスコスト要因)。 + +### Step 3: 採用案を選び、棄却案と理由を記録、インタフェース層を明示 + +採用は 1 案だが、**棄却案も plan.md に残す**。後から「なぜこの設計にしたのか」が辿れる。 + +棄却理由は具体的に書く: +- ❌ 「複雑だから」← 主観的 +- ✅ 「Mixer の blend 構造をカスタム Editor で再現する必要があり、UoW 数が 2 倍になる」← 客観的 +- ✅ 「既存の ScreenSpaceOutline は per-instance 情報を一切使わない GBuffer 自己完結設計であり、per-instance 配線を新規導入する本案はその原則と異質」← 既存原則整合を理由にしてよい + +#### インタフェース層の選定を明示する + +採用設計セクションに以下を明記する(intent.md の動的軸要件 + Step 1 で確認した既存の公開階層と整合させる): + +- **公開階層**: VolumeComponent / MonoBehaviour SerializeField / Material Property / ProjectSettings のどれに公開するか +- **その階層を選んだ理由**: + 例:「intent で『シーン入りで 1 回 ON にする半動的運用』と明記されており、これは既存の `useDistanceFade` と同じ階層 (`HeatDistortionVolume`) で十分。per-object 階層に分散させる理由がない」 + 例:「intent で『完全動的(フレーム単位の切替)』と指定されており、`MaterialPropertyBlock` 経由で per-draw に値を流す経路が必要」 +- **API パターン**: getter / setter の有無、命名、デフォルト値などを既存パターン(例: `Use*` 系の bool 公開)と揃える +- **配置場所**: 既存セクションのどこに挿入するか(既存ヘッダの順序を尊重) + +### Step 4: UoW に分解 + +採用設計を実装単位 (UoW) に分解する。各 UoW は: + +- **対象**: 編集ファイル / ディレクトリ +- **依存**: 他のどの UoW が完了している必要があるか +- **並列可能か**: 領域独立な UoW は同時実装できる(本 PR では並列実行はしないが、依存関係を明示しておくと将来役立つ) +- **担当**: AI 実装 / 人間作業 / 両方 + - **人間作業推奨**: ShaderGraph 編集、Timeline アセット配置、FBX 配置などは Unity Editor 上で対話的にやる方が早い + +UoW 粒度の目安: **1 UoW = コミット 1〜2 個分**。これより大きいと並列化や Phase 3 での進捗管理が難しくなる。 + +### Step 5: コミット先の判断 + +各 UoW がどのリポジトリにコミットされるかを [./sirius-repos.md](./sirius-repos.md) で確認し、plan.md に明記する。 +SIRIUS / SiriusPackages / SiriusAssets のいずれに着地するかで PR の本数が決まる。 + +### Step 6: テンプレートに沿って plan.md を書く + +```bash +cp .agents/skills/ct-ai-dlc/assets/plan-template.md \ + docs/ai-dlc/-/plan.md +``` + +埋める項目: +- 採用設計(**公開階層 / インタフェース設計** を含む) +- 棄却した代替案と理由(既存原則との整合性を観点に含めてよい) +- UoW 一覧(対象 / 依存 / 担当 / コミット先) +- 並列可能ペア +- 触ってはいけないファイル + +### Step 7: 内容の最終確認 + +ユーザーに plan.md の概要を提示する。 +**全文の貼り付けは不要**。要約 + 「全文は `docs/ai-dlc/.../plan.md` 参照」で十分。 + +ユーザーが UoW 分解・採用案・棄却理由について意見を持つ可能性が高いので、 +**「plan.md のリンクを提示し、ユーザーの修正・承認を受ける」運用が望ましい**。 + +### Step 8: 完了メッセージ + +``` +✅ Phase 2 (Inception) 完了 + 出力: docs/ai-dlc/-/plan.md + 採用設計: + UoW: 件 + +次は Phase 3 (Construction) です。 +コンテキストをリセットしてから実行することを推奨します(任意): + (任意)新しい会話を開始し、次の入力で再開 + $ct-ai-dlc <トピック>を実装 +``` + +## このフェーズで ユーザーへの質問 を使う場面 + +- ✅ 採用案が複数の妥当な選択肢に分かれる時 +- ✅ ユーザーが特定の UoW を「自分でやる / AI に任せる」を選ぶ時 +- ❌ 「この UoW 分解で良いですか?」のような全体確認(plan.md を見せて誘導すれば十分) + +## 注意事項 + +- **既存実装を読まずに設計提案しない** — Step 1 のチェックリストをすべて埋めるまで Step 2 に進まない。これを破ると、Phase 2 内で何度も「既存パターンと異質だった」「エンコード式を見落としていた」で案を出し直すことになる +- **既存原則に逸脱する案を採用する時は理由を明示する** — 「新しい仕組みを足したほうが拡張性がある」だけでは不十分。intent の要件 / 性能制約 / 構造的限界など客観的な裏付けを書く +- **公開階層の選定は intent の動的軸を入力にする** — intent.md に「静的 / 半動的 / 完全動的」が書かれていなければ Phase 1 に戻って確認する。動的軸の前提ズレは plan 全体のやり直しに繋がりやすい +- **棄却案を消さない** — 「なぜこの設計か」の歴史的記録。後で「別案で行けば良かった」と気付いた時に参照する +- **人間作業 UoW を明示する** — ShaderGraph や FBX 配置を AI に任せようとすると Phase 3 で破綻する +- **コミット先を明示する** — Phase 3 で「これどこにコミットするんだっけ」となるのを防ぐ +- **Intent の SMAV 不足を後から修正しない** — もし intent.md の Completion Criteria が曖昧で Phase 2 が進まない場合は、Phase 1 に戻る判断をする diff --git a/.agents/skills/ct-ai-dlc/references/phase-3-construction.md b/.agents/skills/ct-ai-dlc/references/phase-3-construction.md new file mode 100644 index 0000000..94814ad --- /dev/null +++ b/.agents/skills/ct-ai-dlc/references/phase-3-construction.md @@ -0,0 +1,215 @@ +# Phase 3: Construction(実装) + +このフェーズでは plan.md に従って実装を進め、コンパイル・テストまでを直列で進める。コミット以降(コミット / push / PR 作成)は git 操作にあたるため、**ユーザーが明示的に許可した場合にのみ実行する**([SKILL.md](../SKILL.md) 共通ルール「git 操作はユーザー許可制」を参照)。許可がなければ Step 4 完了時点(実装+テスト通過)でいったん報告して止まる。 + +## 入力 + +- `docs/ai-dlc/-/plan.md` (Phase 2 の成果物) +- [./sirius-repos.md](./sirius-repos.md) (リポジトリ役割マップ) +- 対象パッケージの `AGENTS.md`, `SKILL.md` + +## 成果物 + +- 各リポジトリ (SIRIUS / SiriusPackages / SiriusAssets) への PR +- (将来) `docs/ai-dlc/-/review-notes.md`, `release-report.md` などを同フォルダに追加可能 + +## 並列発散と直列収束のモデル + +``` +領域独立な実装は並列 OK: + ├ 異なるパッケージのファイル編集 + ├ 別 AGENTS.md / SKILL.md 更新 + └ デモシーン用スクリプトとパッケージ実装の同時進行 + +直列必須(干渉する): + ├ uloop-compile / uloop-run-tests (Unity Editor は単一) + ├ git add / commit / push (index は単一) + ├ Packages/manifest.json 編集 (共有ファイル) + └ 同一ファイルへの書き込み +``` + +**本 PR (AI-DLC 導入の最小構成) では並列実装は使わない**。 +すべての UoW を順次実行する。並列実行は将来サブエージェント導入時に追加する。 + +## 手順 + +### Step 1: plan.md を読み込み、UoW 順序を確定 + +plan.md の依存関係に従って UoW の実行順を確定する。 +人間作業推奨の UoW は AI が実装をスキップし、後で人間が完了させる前提で進める。 + +### Step 2: UoW を 1 件ずつ実装 + +各 UoW について: + +1. plan.md の該当 UoW セクションを読む +2. 対象パッケージの `AGENTS.md`, `SKILL.md` を読む(まだ読んでなければ) +3. 編集対象ファイルを Read してから Edit / Write +4. **判断点があればメッセージで報告**(ユーザーへの質問 ではなく地の文で) + +### Step 3: コンパイルチェック + +UoW 完了ごと、または複数 UoW がひと段落したら: + +``` +$uloop-compile +``` + +エラーが出たら直して再実行。エラーが消えるまで次に進まない。 + +### Step 4: テスト実行(PR マージ前の品質ゲート — MUST) + +**品質ゲート(MUST)**: PR をマージする前に、**macOS(iOS)と Windows(Android)の両方で全 AverageTest(`Assets/Tests/Runtime/AverageTest.cs` の PlayMode ビジュアルリグレッション全ケース + EditMode 全テスト)が成功していること**を必須とする(`AverageTest` は実行 OS の期待ターゲット以外だとスキップされる。ターゲット切替の詳細は Step 4.5 参照)。SIRIUS / SiriusPackages のどちらの CI にも Unity テストの自動ゲートは存在しないため、**AI が手動で TestRunner を実行して担保する**。シェーダ・描画の変更(アウトライン等)は変更対象シーン以外(Shading / PBR / 反射・ポストエフェクト等、同じ描画経路を通る全シーン)にも波及し得るので、**変更箇所に関係なく必ず全件を回す**。一部だけ回して成功しても品質ゲートを満たしたとは見なさない。 + +``` +$uloop-run-tests --test-mode EditMode +$uloop-run-tests --test-mode PlayMode +``` + +**flaky リトライ規則(MUST)**: 一部のテストは何らかの理由(FLIP 閾値近傍のプラットフォーム差等。特に反射・スクリーンスペース系 SSR / SSPR / PlanarReflection / LightShaft で起きやすい)で初回に失敗することがある。試行回数は**初回 + リトライ 2 回 = 最大 3 試行**とし、リトライ対象は**直前の試行で失敗したテストのみ**(成功テストは再実行しない)。**3 試行のうち 1 回でも成功すれば PASS**(flaky 扱い)。**3 試行連続で失敗したテストは「確定失敗」とし、それ以上リトライせず、FLIP Mean / 閾値 / 差分画像パスを添えて ユーザーへの質問 で人間の判断に引き渡す**。flaky か実装起因かの最終判定は人間が行い、実装起因と判断された場合のみ修正して再度全件を成功させる。再実行は失敗ケースだけに絞れる: + +``` +# 単一: exact フィルタ +$uloop-run-tests --test-mode PlayMode --filter-type exact --filter-value 'Tests.Runtime.AverageTest.Test("PostProcess_SSR/SSR",0)' +# 複数: regex フィルタ +$uloop-run-tests --test-mode PlayMode --filter-type regex --filter-value '.*(SSR|SSPR|PlanarReflection|LightShaft).*' +``` + +**実行上の注意(CLI タイムアウト ≠ テスト失敗)**: PlayMode 全件はシーンロード込みで **uloop CLI のリクエストタイムアウト(180 秒)を超える**ことがある。CLI が `Request timed out` を返しても **Unity 側ではテストが完走している**ことが多い。全件は `run_in_background` で実行し、完了後に **`.uloop/outputs/TestResults/.xml`(NUnit 形式)を読んで合否を判定**する。XML は encoding 宣言が不正なことがあるので、先頭の `` を除去してからパースする。失敗があればログも確認: `$uloop-get-logs`。 + +新規シーン追加に伴う期待画像未登録のような「想定内の失敗」は plan.md に追記して許容、それ以外は上記リトライ規則で判定する。 + +### Step 4.5: ビジュアルリグレッション / ビジュアル検証の勘所 + +シェーダ・描画を変更する Phase 3 では、ビジュアルリグレッションテスト (`Assets/Tests/Runtime/AverageTest.cs`、NVIDIA FLIP 画像比較) と手動 screenshot 検証で繰り返しハマる罠がある。グラフィックス変更時は必ず参照すること(2026-05 アウトライン内側食い込み抑制で実際に踏んだ罠を反映)。 + +**ビルドターゲット(最重要)**: +- `AverageTest` は `OneTimeSetUp` の Validate Mobile Target(デフォルト有効)により、**実行 OS の期待ターゲット以外(例: StandaloneOSX)だと `Assert.Ignore` でスキップ**される。期待ターゲットは OS で異なる(`Application.platform` で分岐): **macOS → iOS / WebGL**、**Windows → Android / WebGL**。GraphicsAPI も D3D12/Metal 必須。 +- 正しい pass/fail 判定には実行 OS の期待ターゲット(**macOS なら iOS、Windows なら Android**。WebGL は共通)に切り替えてから実行する。**AI が `uloop execute-dynamic-code` で切替できる**: + ```csharp + using UnityEditor; using UnityEditor.Build; + // macOS の例。Windows では NamedBuildTarget.Android, BuildTarget.Android を指定する + EditorUserBuildSettings.SwitchActiveBuildTarget(NamedBuildTarget.iOS, BuildTarget.iOS); // 戻り値 true で成功 + ``` + - 対象の Build Support モジュール(iOS / Android / WebGL)がインストールされている必要がある。未導入だと切替が false になる — その場合のみ人間に依頼。 + - **別ターゲットへの初回切替はアセット再インポート(テクスチャ圧縮形式の変換等)が走り時間がかかる**。切替後は `uloop compile --wait-for-domain-reload true` 等で落ち着くのを待ってからテストする。 + - 現在のターゲットは `EditorUserBuildSettings.activeBuildTarget`、実行 OS は `Application.platform` で確認できる。 + +**OFF 検証(既存挙動が変わらないことの証明)**: +- 「デフォルト OFF で既存挙動と同一」を最も確実に証明する方法は **git stash でベースライン比較**: + 1. 変更を `git stash` + 2. `uloop execute-menu-item --menu-item-path "Assets/Refresh"` + `uloop compile --wait-for-domain-reload true` でベースラインをビルド + 3. 同じテストを実行し FLIP Mean を記録 + 4. `git stash pop` で変更を戻し、再 Refresh + compile + 同テスト + 5. **FLIP Mean が完全一致すれば変更前後でピクセル単位同一**と実証できる。プラットフォーム差を相殺できるので StandaloneOSX でも判定可能。 + +**ON 検証(新機能の見た目を screenshot で確認)**: +- `uloop control-play-mode --action Play` → `uloop screenshot --window-name Game --capture-mode rendering` で PlayMode のゲーム画面をキャプチャ。 +- **アニメーションを必ず止める**: `uloop execute-dynamic-code` で `Time.timeScale = 0f` を設定してから OFF/ON を撮る。止めないと撮影間の数秒でポーズが変わり、**機能の差分がアニメ差に埋もれて判別不能**になる(本セッションで実際に発生)。 +- OFF/ON は `md5` で同一でないこと(=設定が反映されたこと)を先に確認し、PIL (`ImageChops.difference`) で差分領域・変化ピクセル数・RGB の色傾向を定量化すると確実。1体を crop して 2 倍拡大した OFF/ON 並置画像が人間にも分かりやすい。 + +**uloop execute-dynamic-code の落とし穴**: +- `UnityEngine.Object` は `System.Object` と曖昧になるため **完全修飾**する(`UnityEngine.Object.FindObjectsByType(...)`)。 +- `VolumeProfile` に `GetComponent` は **存在しない**。`profile.TryGet(out var c)` を使う。誤ると無言のコンパイルエラーで設定が一切適用されず、原因(同一画像になる等)に気付きにくい。 +- Volume の値を動的に変えるときは **`sharedProfile` を変更**する。`volume.profile`(ランタイム instance)経由の変更が VolumeManager のスタックに反映されないことがある。アセットを変えるので **検証後に必ず元に戻す**。 +- 戻り値の `Result` が空のときはたいてい無言のコンパイルエラー。`grep` で絞らず出力全体(`CompilationErrors` / `ErrorCode`)を確認すること。 + +### Step 5: コミット + +> **このステップ以降(コミット / push / PR)は git 操作。実行前にユーザーの明示的な許可を得ること**([SKILL.md](../SKILL.md) 共通ルール「git 操作はユーザー許可制」)。許可がなければ Step 4 完了時点で報告して止まり、以降の手順は許可が出てから進める。以下は **許可された後に従う手順**。 + +変更の性質ごとにコミットを分ける。コミット先は plan.md に書いた通り。 + +**重要なルール:** +- コミットメッセージは **日本語**(AGENTS.md 規約) +- `git add` は **ファイル名を明示**(`-A` / `.` は機密ファイル混入のリスク) +- `.meta` ファイルは Unity 生成のものをそのままコミット(AI が編集したものはコミット前に検出) + +```bash +# パッケージ実体 +git add SiriusPackages/Sirius.PostProcessing/... +git commit -m "Impact Frames Pass を追加" + +# デモシーン +git add Assets/Demo/ImpactFrames/... +git commit -m "Impact Frames デモシーンを追加" +``` + +### Step 6: ブランチ作成と push + +AGENTS.md 規約: **PR を作成するときは必ず `origin/main` から新しいブランチを作成**。 + +```bash +git checkout -b feat/impact-frames origin/main +# (コミットを cherry-pick または再作業) +git push -u origin feat/impact-frames +``` + +### Step 7: PR 作成 + +本文は [assets/pr-template.md](../assets/pr-template.md) を雛形にする(lean / 署名なし)。 + +**7-1: 変更の有無を確認** + +feature ブランチが `origin/main` より先行していることを確認する: + +```bash +git rev-list --count origin/main..HEAD +``` + +**7-2: パッケージドキュメント更新** + +`SiriusPackages/` のパッケージ実体が変わっている場合、[$ct-update-pkg-docs](../../ct-update-pkg-docs/SKILL.md) の手順で該当パッケージの AGENTS.md / SKILL.md を更新し、**実装とは別コミット**にする。 + +**7-3: ブランチを push** + +PR 作成にはリモートブランチが必要。未 push のブランチは push する(push は外部反映なので、未確認なら一言ことわってから実行)。 + +**7-4: PR を作成** + +`assets/pr-template.md` の `<...>` を埋め、ガイドコメントを除去した本文を一時ファイルに書き出して作成する。タイトルは変更の本質を簡潔に表す(70 文字以内目安)。 + +```bash +gh pr create --base main \ + --title "<変更の要点を簡潔に>" --body-file /tmp/pr-body.md +``` + +- 概要に既定値変更・追加アセット・lockfile 差分の意図を明記する。最低 Unity 版を上げたら `version` メジャー bump する。 +- **テスト欄は品質ゲート(macOS→iOS / Windows→Android の両方で全 AverageTest 成功、リトライ規則)を実際に確認した上で `[x]`** にする。テスト欄はプラットフォーム別に 2 チェック項目へ分かれている。 +- 作成前に PR タイトルと本文を提示し、**ユーザーへの質問 で確認してから作成**する。 + +**7-5: 関連 PR のクロスリンク(複数 PR に分けた場合のみ)** + +関連する PR が複数あるときは、URL が出揃ってから各 PR 本文の「関連 PR」に相互リンクを追記する(本文を再生成して `gh pr edit --body-file` で差し替え): + +```bash +gh pr edit --body-file /tmp/pr-body-A-linked.md # 関連に PR_B を追記 +gh pr edit --body-file /tmp/pr-body-B-linked.md # 関連に PR_A を追記 +``` + +→ どの PR からも双方向にたどれる状態にする。完成した PR URL は Step 8 の完了メッセージに含める。 + +### Step 8: 完了メッセージ + +``` +✅ Phase 3 (Construction) 完了 + 作成 PR: + - PR #XXX + 関連 plan: docs/ai-dlc/-/plan.md + +次は Phase 4 (Review) です。PR にレビュアーを割り当て、 +チームで使っている連絡手段でレビュー依頼を送ってください。 +``` + +## このフェーズで ユーザーへの質問 を使う場面 + +- ✅ テスト失敗の対応方針(無視 / 修正) +- ✅ PR 作成時のコミット粒度・タイトル +- ❌ 「次の UoW に進んでいいですか?」のような形式的確認 + +## 注意事項 + +- **plan.md と異なる実装をした場合は plan.md を更新する** — 「採用設計」と「実装」が食い違う状態を残さない +- **判断点をメッセージで報告する** — ユーザーが気付ける形にする(ユーザーへの質問 で止めないが、報告は重要) +- **`.meta` ファイルを AI が編集していないか確認** — Unity Editor が生成したものに限定(AGENTS.md 規約、PR #553 教訓) +- **PR を分けるべき変更は分ける** — 1 PR にしすぎると review コストが増える。plan.md の「PR 本数」見積もりを尊重する diff --git a/.agents/skills/ct-ai-dlc/references/sirius-repos.md b/.agents/skills/ct-ai-dlc/references/sirius-repos.md new file mode 100644 index 0000000..5bbf5a5 --- /dev/null +++ b/.agents/skills/ct-ai-dlc/references/sirius-repos.md @@ -0,0 +1,61 @@ +# リポジトリ構成マップ + +Phase 2 (Inception) で UoW の配置先を決める時、Phase 3 (Construction) で実装に入る時に参照する。 + +このファイルは **「どこに何を置き、どうコミットを分けるか」** に特化した参照資料。 +規約事項(`.meta` の扱い、ブランチ運用など)は AGENTS.md に一元化されているので、そちらを参照すること(末尾の「規約は別ファイル」セクション参照)。 + +## ディレクトリの役割 + +本リポジトリは単一リポジトリ構成で、次の3系統から成る。 + +| ディレクトリ | 役割 | 主な内容 | +|---|---|---| +| `SiriusPackages/` | UPM パッケージ実装本体 | `Sirius.Core` / `Sirius.PostProcessing` / `Sirius.DevSupport` | +| `SiriusAssets/` | 配布アセット | `Sirius.Core.Assets` | +| ルート直下 | デモアプリ + AI エージェント作業場 + テストハーネス + ドキュメント | `Assets/Demo/`, `Assets/Tests/`, `Assets/Settings/`, `.agents/`, `docs/`, `LocalPackages/`, `Packages/` | + +`Packages/manifest.json` は `file:../SiriusPackages/...` / `file:../SiriusAssets/...` でこれらをローカル参照している。 + +## 編集対象と配置先のマトリクス + +UoW ごとに「何を編集するか」が決まれば、配置先がこの表から決まる。 + +| 作業 | 編集対象 | 配置先 | +|---|---|---| +| Intent 起票 | intent.md | `docs/ai-dlc/-/` | +| Inception | plan.md | `docs/ai-dlc/-/` | +| パッケージ実装 | C# / Shader / hlsl | `SiriusPackages/Sirius.*/` | +| 配布アセット | mat / asset / prefab | `SiriusAssets/*` | +| デモシーン | Scene / Animation / Material / FBX | `Assets/Demo//` | +| テスト期待画像 | png | `Assets/Tests/SuccessfulImages/` | +| 公開ドキュメント | README | `README.md` | +| Codex 資産 | SKILL.md / agent 定義 | `.agents/skills/`, `AGENTS.md` | +| 開発者向け文書 | README_DEVELOPERS | `README_DEVELOPERS.md` | +| tarball 検証 (一時) | .tgz | `LocalPackages/` → **コミットしない** | +| manifest 切替 (一時) | manifest.json | `Packages/manifest.json` → **コミットしない** | + +## コミットの分け方 + +単一リポジトリなので PR は原則 1 本。ただし **コミットは性質ごとに分ける**と後から追いやすい。 + +| 機能の性質 | コミットの分け方 | +|---|---| +| パッケージ単独(既存 Pass 内部最適化など) | 実装 1 コミット | +| パッケージ + デモ | パッケージ実装 / デモシーン の 2 コミット | +| パッケージ + アセット + デモ | 3 コミット | +| スキル / エージェント追加・改善 | `.agents/` + `README_DEVELOPERS.md` で 1 コミット | +| AI-DLC フロー自体の改善 | 本スキルや reference の編集で 1 コミット | + +`SiriusPackages/` のパッケージ実体を変えた場合は、該当パッケージの AGENTS.md / SKILL.md 更新を **実装とは別コミット**にする。 + +## 規約は別ファイル + +このファイルでは規約を再掲しない。実装着手前に以下を必ず確認すること(AGENTS.md からも参照されている): + +| 規約 | 参照先 | +|---|---| +| `.meta` ファイルの AI 編集禁止 | [AGENTS.md](../../../../AGENTS.md) | +| PR 作成時は `origin/main` から新ブランチ | [AGENTS.md](../../../../AGENTS.md) | + +迷ったら AGENTS.md を読む。このファイルは「**どこに何を置き、どうコミットを分けるか**」だけを答える。 diff --git a/.agents/skills/ct-pkg-sirius-core/SKILL.md b/.agents/skills/ct-pkg-sirius-core/SKILL.md new file mode 100644 index 0000000..4effbc6 --- /dev/null +++ b/.agents/skills/ct-pkg-sirius-core/SKILL.md @@ -0,0 +1,53 @@ +--- +name: ct-pkg-sirius-core +description: "Sirius.Coreパッケージの設計詳細。Use when: (1) Core機能の修正・拡張, (2) ワークショップのシェーダーが参照する共通hlslの理解, (3) 他パッケージが依存する共通基盤の変更" +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + +ソースの相対パスは `SiriusPackages/Sirius.Core/` 基準。`references/` はこのスキルのディレクトリ基準。作業前に `SiriusPackages/Sirius.Core/AGENTS.md` を読む。 + + +# Sirius.Core 設計詳細 + +ワークショップ用に最小化された共通基盤。ポストエフェクトのシェーダーが参照する hlsl と、 +それを動かすのに必要な最小限の C# のみを持つ。 + +## パッケージ間の依存関係 + +``` +Sirius.Core ← Sirius.PostProcessing (asmdef参照) + ← Sirius.DevSupport は依存しない(独立) +``` + +asmdef が参照するのは `Unity.RenderPipelines.Core.Runtime` のみ。 +hlsl 側は URP の Universal / Core 両方のシェーダーライブラリを include する。 + +## 構成物 + +### Runtime/Shaders + +| ファイル | 役割 | 主な利用先 | +|---|---|---| +| `Common.hlsl` | `SIRIUS_PREFIX*` マクロ、`SIRIUS_USE_CLUSTERED_LIGHT_LOOP` 判定 | ワーク④ | +| `CoreUtil.hlsl` | `pow2`〜`pow8` / `powFast` / `acosFast` / `Panner` / `RotateAboutAxis` などのユーティリティ | (現在参照ワークなし。共通ユーティリティとして提供) | +| `DeclareDepthTexture.hlsl` | `SampleSceneDepth` / `LoadSceneDepth` の Unity バージョン差異吸収 | `CoreUtil.hlsl` | +| `ScreenSpaceUtil.hlsl` | 深度からのワールド/ビュー座標復元(`GetWorldPosition` / `GetCameraDistance`)、Framebuffer Fetch 抽象化 | ワーク④(HeatDistortion) | + +`ScreenSpaceUtil.hlsl` はワーク④の**足場**として提供している。参加者は距離フェード実装時にこれを include する +(`Sirius.PostProcessing/Runtime/Shaders/HeatDistortion.shader` にコメントでヒントを置いてある)。 + +### Runtime/Scripts + +| ファイル | 役割 | +|---|---| +| `CTRenderGrapProfilingScope.cs` | `AddUnsafePass` を使う RenderGraph 用プロファイリングスコープ。URP の `RenderGraphProfilingScope` が `AddRenderPass` 前提で使えないため自前実装。(現在参照ワークなし) | +| `GlobalSettings.cs` | `DevelopmentMode` のみ。`CTRenderGraphProfilingScope` が参照する | +| `SiriusIgnorer.cs` | カメラ単位でポストエフェクトの実行を除外するコンポーネント。`SiriusPostProcessingFeature` が参照 | + +## 注意 + +- `GlobalSettings.DevelopmentMode` を設定する RendererFeature は無い。Editor / DEVELOPMENT_BUILD では既定 `true`、それ以外では常に `false` になる +- ワークショップ縮小版のため、製品版 Sirius.Core にある GBuffer / Fog / Quality / WindZone 連携・Editor 用 MaterialGUI 基盤は含まれない diff --git a/.agents/skills/ct-pkg-sirius-devsupport/SKILL.md b/.agents/skills/ct-pkg-sirius-devsupport/SKILL.md new file mode 100644 index 0000000..dd2b95e --- /dev/null +++ b/.agents/skills/ct-pkg-sirius-devsupport/SKILL.md @@ -0,0 +1,21 @@ +--- +name: ct-pkg-sirius-devsupport +description: "Sirius.DevSupportパッケージの設計詳細。Use when: (1) 開発支援ツールの修正・拡張, (2) GraphicsRegressionTest/ShaderPerformanceAnalysis/GPUProfiler設計の理解" +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + +ソースの相対パスは `SiriusPackages/Sirius.DevSupport/` 基準。`references/` はこのスキルのディレクトリ基準。作業前に `SiriusPackages/Sirius.DevSupport/AGENTS.md` を読む。 + + +# Sirius.DevSupport 設計詳細 + +開発支援パッケージ。他のSiriusパッケージに依存しない独立設計。 + +## 詳細リファレンス + +機能別の詳細は以下のサブドキュメントを参照。対象機能に関連するファイルを Read して使用すること。 + +- **GraphicsRegressionTest**: `references/regression-test.md` — CameraTestProvider / TestSceneProvider の構成、NVIDIA FLIP 画像比較 diff --git a/.agents/skills/ct-pkg-sirius-devsupport/references/regression-test.md b/.agents/skills/ct-pkg-sirius-devsupport/references/regression-test.md new file mode 100644 index 0000000..c5ebb8b --- /dev/null +++ b/.agents/skills/ct-pkg-sirius-devsupport/references/regression-test.md @@ -0,0 +1,9 @@ +# GraphicsRegressionTest の構成 + +Editor専用の別asmdef(`Sirius.DevSupport.GraphicsRegressionTest.Editor`)。 + +2つのテストプロバイダがある: +- **CameraTestProvider**: `CameraTestConfig` (ScriptableObject) に定義されたカメラ設定でテスト。`CameraPrefabTestContext` がPrefab配置→撮影→比較を管理 +- **TestSceneProvider**: シーン単位テスト。`TestSceneParameter` で対象シーンとパラメータを定義 + +画像比較は **NVIDIA FLIP** アルゴリズム(`NvidiaFlip`)を使用。知覚的な差異を検出し、`FlipAssert` が閾値ベースで合否判定。 diff --git a/.agents/skills/ct-pkg-sirius-postprocessing/SKILL.md b/.agents/skills/ct-pkg-sirius-postprocessing/SKILL.md new file mode 100644 index 0000000..231679b --- /dev/null +++ b/.agents/skills/ct-pkg-sirius-postprocessing/SKILL.md @@ -0,0 +1,40 @@ +--- +name: ct-pkg-sirius-postprocessing +description: "Sirius.PostProcessingパッケージの設計詳細。Use when: (1) ポストエフェクトの修正・拡張, (2) パス設計・Volume/AllowFlagパターンの理解" +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + +ソースの相対パスは `SiriusPackages/Sirius.PostProcessing/` 基準。`references/` はこのスキルのディレクトリ基準。作業前に `SiriusPackages/Sirius.PostProcessing/AGENTS.md` を読む。 + + +# Sirius.PostProcessing 設計詳細 + +ポストエフェクトをVolumeベースで制御するパッケージ(ワークショップ用縮小版)。 + +## AllowFlag パターンの仕組み + +新規パス追加時の規約は AGENTS.md を参照。ここではパターンの動作を説明する。 + +`AddRenderPasses` 内で `allow*` フラグをチェックし、true の場合のみ `EnqueuePass` する。Volume を持つエフェクトは、パス内部で Volume の値を参照して効果量を決める(例: DirectionalBlurVolume / RadialBlurVolume)。 + +## パス一覧と登録条件 + +| パス | allow フラグ | Volume | 追加条件 | +|------|------------|--------|---------| +| RadialBlurRenderPass | `allowRadialBlurPostProcess` | RadialBlurVolume | - | +| DirectionalBlurRenderPass | `allowDirectionalBlurPostProcess` | DirectionalBlurVolume | - | +| HeatDistortionRenderPass | `allowHeatDistortionPostProcess` | HeatDistortionVolume | active、Intensity > 0、Texture3D 設定済み | + +シェーダーは `Runtime/Shaders/`(`DirectionalBlur.shader` / `RadialBlur.shader` / `RotationBlur.shader`)。 +`Editor/Scripts/ShaderIncluder.cs` がリフレクションで各パスの `UsingShaderNameList` を収集し、ビルドの AlwaysIncludedShaders へ登録する。 + +## HeatDistortion + +`HeatDistortionVolume` は Intensity / StartDistance / FadeDistance / NoiseTexture / NoiseScale / Speed / HorizonExponent を公開する。強度は既定0。`NoiseTexture` には `Packages/jp.co.cyberagent.sirius.core.assets/Assets/3DCells64Sheet.png`(64³ Texture3D)を割り当てる。 + +実行中の変更はシーン Volume の `profile.TryGet(out var heat)` で取得した runtime profile の `heat.Intensity` を変更する。対象Parameterの overrideState を事前に有効にする(全項目なら `heat.SetAllOverridesTo(true)`)。VolumeManager.stack は書き換えない。デモ `HeatDistortionDemo` に強度スライダーがある。 + +パスは BeforeRenderingPostProcessing の1イベント前。深度入力を要求し、RenderGraph にカラーと深度の read 依存を宣言して別カラーへ描画する。強度0・ノイズ未設定では enqueue しない。カメラ前方の連続したワールド空間の面で3Dノイズを参照し、距離・水平視線・画面端のマスクを適用する。深度は移動元と移動先の距離フェードに使い、開始距離より手前の近景を保護する。空だけの領域は維持するが、物体との境界は同じ歪みで移動させる。表面深度によるノイズ座標の切替や、深度差だけを理由とした元UVへの復帰は、元輪郭が残る原因になるため行わない。深度を書かない透明物は背後の深度に従う。 diff --git a/.agents/skills/ct-update-pkg-docs/SKILL.md b/.agents/skills/ct-update-pkg-docs/SKILL.md new file mode 100644 index 0000000..e64636a --- /dev/null +++ b/.agents/skills/ct-update-pkg-docs/SKILL.md @@ -0,0 +1,144 @@ +--- +name: ct-update-pkg-docs +description: "SiriusPackagesのCLAUDE.mdとSKILL.mdを最新のソースコードに基づいて更新する" +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + +Claude 側の原本を更新後、リポジトリルートで `python3 scripts/sync_codex_skills.py` を実行し、Codex の skills とパッケージ AGENTS.md に同期する。生成先を直接編集しない。 + + +# SiriusPackages ドキュメント更新スキル + +SiriusPackages の各パッケージに配置された CLAUDE.md(ルール・制約)と SKILL.md(設計詳細)を、最新のソースコードに基づいて更新する。 + +## 対象パッケージとファイルパス + +| パッケージ | CLAUDE.md | SKILL.md | +|-----------|-----------|----------| +| Sirius.Core | `SiriusPackages/Sirius.Core/CLAUDE.md` | `SiriusPackages/Sirius.Core/.claude/skills/ct-pkg-sirius-core/SKILL.md` | +| Sirius.PostProcessing | `SiriusPackages/Sirius.PostProcessing/CLAUDE.md` | `SiriusPackages/Sirius.PostProcessing/.claude/skills/ct-pkg-sirius-postprocessing/SKILL.md` | +| Sirius.DevSupport | `SiriusPackages/Sirius.DevSupport/CLAUDE.md` | `SiriusPackages/Sirius.DevSupport/.claude/skills/ct-pkg-sirius-devsupport/SKILL.md` | + +--- + +## 実行フロー + +### Step 1: 対象パッケージの選択 + +ユーザー指定があれば採用し、未指定なら質問して更新対象を選ぶ(複数指定可): +- Sirius.Core +- Sirius.PostProcessing +- Sirius.DevSupport + +### Step 2: 各パッケージについて以下を実行 + +#### 2-1: 現状把握 + +1. 既存の CLAUDE.md と SKILL.md を Read ツールで読む +2. パッケージの主要ソースファイルを読む: + - `package.json` — 依存関係の変化 + - `Runtime/**/*.asmdef` — アセンブリ参照の変化 + - Feature クラス (`*Feature.cs`) — パス登録の変化 + - Volume クラス (`*Volume.cs`) — 新しいVolume追加 + - 追加・変更されたファイル(git diff で検出) + +#### 2-2: 差分分析 + +以下を確認し、更新が必要な箇所を特定する: +- 新しいクラス/パスの追加・削除 +- 既存パスの登録条件変更 +- パッケージ間の依存関係変更 +- 新しいゴッチャ・制約の発見 + +変更が見つからない場合は「変更なし」と報告してそのパッケージをスキップする。 + +#### 2-3: CLAUDE.md 更新案の作成 + +**後述の CLAUDE.md ポリシーに厳密に従って**更新案を作成する。 +変更箇所のみを差分で表示する(全文書き直しではなく、追加・変更・削除を明示)。 + +#### 2-4: SKILL.md 更新案の作成 + +**後述の SKILL.md ポリシーに厳密に従って**更新案を作成する。 +変更箇所のみを差分で表示する。 + +SKILL.md の frontmatter(name, description)は変更しない。 + +#### 2-5: ユーザー確認 + +各パッケージごとに更新内容のサマリを表示し、ユーザーへの質問 で確認する: +- 適用する +- 修正して適用する(フィードバックを受けて再作成) +- スキップする + +#### 2-6: ファイル書き込み + +承認されたら Edit ツールで更新を適用する。 + +### Step 3: 完了報告 + +更新したファイル一覧と主な変更点を表示する。 + +--- + +## CLAUDE.md ポリシー(厳守) + +CLAUDE.md はパッケージ固有の **ルール・制約・ゴッチャ** のみを記載するファイル。 + +### 含めるべきもの + +- **MUST**: 違反するとバグ・ビルドエラーになるルール +- **IMPORTANT**: 知らないと間違いやすいゴッチャ・落とし穴 +- 非自明な制約(コードを読んだだけでは気づきにくいもの) + +### 含めてはならないもの + +以下は **絶対に含めない**: +- パッケージ情報(名前、バージョン、依存関係)— `package.json` を読めばわかる +- アセンブリ構成 — `.asmdef` を読めばわかる +- クラスやインターフェースの説明 — ソースを読めばわかる +- 設計パターンの説明 — SKILL.md の役割 +- 「〜を担当する」「〜を管理する」のような機能説明 + +### フォーマット + +- **MUST** / **IMPORTANT** で重要度を明示する +- 見出し(`##`)でカテゴリ分け +- 箇条書きで簡潔に記述 +- **50行以内**を目標とする + +### 検証テスト + +各行に対して「この行を削除したら、AI がこのパッケージで作業する際に間違いを犯すか?」を自問する。答えが No なら削除する。 + +--- + +## SKILL.md ポリシー(厳守) + +SKILL.md はパッケージの **設計詳細** を記載するファイル。コードの1ファイルからは読み取れない、アーキテクチャレベルの知識を提供する。 + +### 含めるべきもの + +- **コンポーネント間の関係性**: パッケージ横断の依存、暗黙的な連携(例: Bloom→GBuffer) +- **パス登録順序と条件のサマリテーブル**: 複数パスの全体像 +- **設計パターンの「なぜ」**: AllowFlagパターン等の規約の理由 +- **非自明な設計判断の理由**: なぜこの実装になっているかの背景 +- **ワークフロー/パイプライン**: 複数Stepにまたがる処理の流れ + +### 含めてはならないもの + +以下は **絶対に含めない**: +- ASCIIディレクトリツリー — `Glob` ツールで取得可能。最も陳腐化しやすい +- ソースコードのコピペ(インターフェース定義、クラス定義等)— ソースを読めばわかる +- Enum一覧テーブル — ソースを読めばわかる +- フィールド/プロパティの列挙 — ソースを読めばわかる +- CLAUDE.md と重複する内容 — ルール・制約は CLAUDE.md に一本化 + +### フォーマット + +- frontmatter の name / description は既存のものを維持する +- テーブル形式はパスサマリ等の全体像把握に有効。積極的に使う +- コードブロックは設計パターンの例示(数行以内)にのみ使用 diff --git a/.agents/skills/ct-update-pkg-docs/agents/openai.yaml b/.agents/skills/ct-update-pkg-docs/agents/openai.yaml new file mode 100644 index 0000000..5b1f887 --- /dev/null +++ b/.agents/skills/ct-update-pkg-docs/agents/openai.yaml @@ -0,0 +1,2 @@ +policy: + allow_implicit_invocation: false diff --git a/.agents/skills/uloop-clear-console/SKILL.md b/.agents/skills/uloop-clear-console/SKILL.md new file mode 100644 index 0000000..238cf2b --- /dev/null +++ b/.agents/skills/uloop-clear-console/SKILL.md @@ -0,0 +1,53 @@ +--- +name: uloop-clear-console +description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + + +# uloop clear-console + +Clear Unity console logs. + +## Usage + +```bash +uloop clear-console [--add-confirmation-message] +``` + +## Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `--add-confirmation-message` | boolean | `false` | 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/.agents/skills/uloop-compile/SKILL.md b/.agents/skills/uloop-compile/SKILL.md new file mode 100644 index 0000000..584d0f0 --- /dev/null +++ b/.agents/skills/uloop-compile/SKILL.md @@ -0,0 +1,75 @@ +--- +name: uloop-compile +description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + + +# uloop compile + +Execute Unity project compilation. + +## Usage + +```bash +uloop compile [--force-recompile] [--wait-for-domain-reload] +``` + +## Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | +| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | + +## 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 + +# Force full recompilation +uloop compile --force-recompile + +# Force recompilation and wait for Domain Reload completion +uloop compile --force-recompile true --wait-for-domain-reload true + +# Wait for Domain Reload completion even without force recompilation +uloop compile --force-recompile false --wait-for-domain-reload true +``` + +## Output + +Returns JSON: +- `Success`: boolean +- `ErrorCount`: number +- `WarningCount`: number + +## Troubleshooting + +Diagnose the failure mode before retrying. + +**Stale lock files** (CLI hangs or shows "Unity is busy" while Unity Editor *is* running): + +```bash +uloop fix +``` + +This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `uloop compile`. + +**Unity Editor not running** (CLI returns a connection failure and no Unity process is alive): + +```bash +uloop launch +``` + +`uloop launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `uloop compile`. diff --git a/.agents/skills/uloop-control-play-mode/SKILL.md b/.agents/skills/uloop-control-play-mode/SKILL.md new file mode 100644 index 0000000..365d74b --- /dev/null +++ b/.agents/skills/uloop-control-play-mode/SKILL.md @@ -0,0 +1,60 @@ +--- +name: uloop-control-play-mode +description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + + +# 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` | + +## 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 + +# Stop play mode +uloop control-play-mode --action Stop + +# Pause play mode +uloop control-play-mode --action Pause +``` + +## Output + +Returns JSON with the current play mode state: +- `IsPlaying`: Whether Unity is currently in play mode +- `IsPaused`: Whether play mode is paused +- `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 +- Pause action pauses the game while remaining in play mode +- Useful for automated testing workflows + +- PlayMode entry may complete on the next editor frame. If a PlayMode-dependent command reports "PlayMode is not active" immediately after `--action Play`, wait briefly and retry. diff --git a/.agents/skills/uloop-execute-dynamic-code/SKILL.md b/.agents/skills/uloop-execute-dynamic-code/SKILL.md new file mode 100644 index 0000000..8a78612 --- /dev/null +++ b/.agents/skills/uloop-execute-dynamic-code/SKILL.md @@ -0,0 +1,91 @@ +--- +name: uloop-execute-dynamic-code +description: "Execute C# with Unity APIs when existing uloop tools cannot inspect or edit enough. Use for scene, prefab, SerializedObject, AssetDatabase refresh/.meta generation, menu, or PlayMode automation." +--- + + + +このスキル内の Read / Edit / Write / Glob / Grep / Bash は操作の説明であり、Codex では利用可能なファイル・シェルツールで行う。質問は利用可能な質問ツール、承認は通常の会話で行う。既に得たユーザーの指示・許可を優先し、同じ許可を再確認しない。 + + +# Task + +Execute the following request using `uloop execute-dynamic-code`: $ARGUMENTS + +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. + +## Workflow + +1. Read the relevant reference file(s) from the Code Examples section below +2. Construct C# code based on the reference examples +3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `uloop execute-dynamic-code --code-file ` +4. Use `uloop execute-dynamic-code --code ''` only for short one-line snippets +5. If execution fails, adjust code and retry +6. Report the execution result + +## Parameters + +- `--code ''`: Inline C# statements to execute. Use this for one-line snippets only. +- `--code-file `: Read C# statements from a UTF-8 file. Prefer this for multiline snippets, especially on PowerShell, because Windows `.cmd` shims can lose lines from multiline inline arguments before `uloop` receives them. +- **Shell quoting**: bash/zsh uses single quotes, for example `uloop execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `uloop execute-dynamic-code --code 'Debug.Log("Hello!");'`. +- `--parameters {}` (advanced, optional): Pass an object 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, and pass an object instead of a JSON string. +- `--compile-only true` (optional): Compile the snippet without executing it. Use this when you want Roslyn diagnostics before running new code. + +## Code Rules + +Write direct statements only — no class/namespace/method wrappers. Return is optional. + +```csharp +using UnityEngine; +float x = Mathf.PI; +return x; +``` + +**Forbidden** — these will be rejected at compile time: `System.IO.*`, `AssetDatabase.CreateFolder`, creating/editing `.cs`/`.asmdef` files. Use terminal commands for file operations instead. + +## Output + +Returns JSON: +- `Success`: boolean — overall execution success +- `Result`: string — value of the snippet's `return` statement (empty when omitted) +- `Logs`: string[] — execution diagnostics from the dynamic-code runner, not Unity Console entries +- `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` +- `SecurityLevel`: string — dynamic-code security level active for the request +- `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 + +Use `uloop get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, 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 (zsh)**: 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 automation (PowerShell)**: 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 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/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md new file mode 100644 index 0000000..31ddf7a --- /dev/null +++ b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -0,0 +1,194 @@ +# 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/.agents/skills/uloop-execute-dynamic-code/references/batch-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/batch-operations.md new file mode 100644 index 0000000..875c3fc --- /dev/null +++ b/.agents/skills/uloop-execute-dynamic-code/references/batch-operations.md @@ -0,0 +1,399 @@ +# 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/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md new file mode 100644 index 0000000..40eec9a --- /dev/null +++ b/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md @@ -0,0 +1,403 @@ +# 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/.agents/skills/uloop-execute-dynamic-code/references/material-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/material-operations.md new file mode 100644 index 0000000..3bd9869 --- /dev/null +++ b/.agents/skills/uloop-execute-dynamic-code/references/material-operations.md @@ -0,0 +1,160 @@ +# 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); + +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); + +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); + +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/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md new file mode 100644 index 0000000..dd6fb01 --- /dev/null +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -0,0 +1,330 @@ +# 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 Quoting Notes + +Use these patterns when you need shell-safe inline code: + +### Double quotes inside C# strings + +Single-quote the whole snippet and keep C# string literals unchanged. + +```powershell +uloop execute-dynamic-code --code 'return "Hello from PowerShell";' +``` + +### Single quotes inside inline C# code + +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();' +``` + +### JSON-like values passed via `--parameters` + +Wrap the whole expression in single quotes so PowerShell passes the inner double quotes through unchanged. + +```powershell +uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' +``` + +### Multi-line C# snippets + +Use a here-string when the snippet spans multiple lines. + +```powershell +$code = @' +using UnityEngine; + +GameObject obj = GameObject.Find("Player"); +if (obj == null) return "Player not found"; + +return obj.name; +'@ + +uloop execute-dynamic-code --code $code +``` + +You can combine multi-line code with multi-line parameters the same way. + +```powershell +$code = @' +return parameters["param0"]; +'@ + +$parameters = @' +{"param0":"Hello from PowerShell"} +'@ + +uloop execute-dynamic-code --code $code --parameters $parameters +``` + +## Click UI Button by Path + +```csharp +using UnityEngine.UI; + +Button btn = GameObject.Find("Canvas/StartButton")?.GetComponent