diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 6412ce7..b4d1947 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -123,13 +123,18 @@ Unity **6000.3.6f1** (Unity 6 LTS) is installed locally at project lives at `TestProject/` at the repo root (non-tilde name — Unity batchmode rejects `~`-suffixed project paths). -Open via Unity Hub (point it at `TestProject/`) or headless: +Drive Unity through the [`unity` CLI](https://docs.unity.com/en-us/unity-cli) +(on `PATH`; `unity --help`, `unity doctor` to check it). It resolves the +editor from `ProjectVersion.txt`, so no hard-coded editor path is needed: ```bash -"/Applications/Unity/Hub/Editor/6000.3.6f1/Unity.app/Contents/MacOS/Unity" \ - -batchmode -nographics -projectPath TestProject -logFile - -quit +unity open TestProject # GUI editor +unity run TestProject -- -nographics -logFile - # headless batch run (never pass -batchmode / -quit / -projectPath) ``` +For the full CLI reference, `unity skill install claude-code` installs +Unity's own agent skill user-globally. + ### Edit Mode tests Run the Unity Test Runner in headless batchmode: @@ -152,19 +157,48 @@ in the VS Code Problems panel. > the test runner fires and exits silently with no results. The test runner > exits the editor itself once tests finish. -### Driving a live Unity Editor via MCP +`unity test TestProject --mode EditMode -- -nographics` runs the same suite +without the problem-matcher summary. It exits `8` when tests fail and `6` when +the run never produced a verdict (compile error, license, editor already open +on the project). + +### Driving a live Unity Editor + +`unity command` drives a running editor through its Pipeline server, which +needs the `com.unity.pipeline` package in that project. Neither `TestProject` +nor Getting Started has it committed; add it with +`unity pipeline install --project-path `. -When the user has Unity open, [unity-mcp](https://github.com/Unity-Technologies/unity-mcp) -exposes `Unity_ManageEditor`, `Unity_RunCommand`, `Unity_GetConsoleLogs`, -`Unity_SceneView_*`, etc. Call `Unity_ManageEditor Action=GetState` first — -the live editor is often against `/Users/kraenhansen/UnityProjects/Getting Started`, +The live editor is often against `/Users/kraenhansen/UnityProjects/Getting Started`, which references this package via `file:` with `testables: ["io.elevenlabs.agents"]`, -so our runtime + samples + PlayMode tests are reachable from there. +so our runtime + samples + PlayMode tests are reachable from there. Always pass +`--project-path`, since otherwise the target follows the shell's cwd: + +```bash +GS="/Users/kraenhansen/UnityProjects/Getting Started" +unity status # GUI editors show state "ready" +unity command --project-path "$GS" --query console # discover commands; omit --query for the full catalog +unity command eval 'return UnityEngine.Application.unityVersion;' --project-path "$GS" +unity command console --tail 50 --level warn --project-path "$GS" +unity command run_tests --project-path "$GS" # tests in the live editor (incl. PlayMode) +unity command capture_scene_view --save_path shot.png --project-path "$GS" +``` -Caveats: PlayMode audio callbacks (`PCMReaderCallback` etc.) only fire when -the Unity Editor window is focused — ask the user to focus it before -measuring. `Unity_GetConsoleLogs` sometimes returns empty; -`~/Library/Logs/Unity/Editor.log` is the reliable fallback. +`unity mcp` serves the same command catalog as MCP tools +(`unity mcp configure claude-code` registers it); either route works. + +Caveats: + +- PlayMode audio callbacks (`PCMReaderCallback` etc.) only fire when the + Unity Editor window is focused — ask the user to focus it before measuring. +- A batch-mode editor launched without `-quit` stays resident and serves + commands, but `unity status` doesn't list it; probe with + `unity list --project-path `. `unity command quit` fails in batch + mode; stop it with `unity close --force`. +- Compile errors put the editor in Safe Mode, where the Pipeline server + doesn't load and every command fails to connect. Confirm with + `unity pipeline list`, then read the errors from + `~/Library/Logs/Unity/Editor.log` (grep for `error CS`). ## Unity-owned `.meta` files diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7feca63..65640eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -161,7 +161,9 @@ interface seams instead. stream Unity's log live). Failures are formatted for the VS Code problem matcher in [`.vscode/tasks.json`](.vscode/tasks.json). Requires Unity **6000.3.6f1** at the path `/Applications/Unity/Hub/Editor/6000.3.6f1/Unity.app/Contents/MacOS/Unity` (or override via -`$UNITY` / `--unity`). +`$UNITY` / `--unity`). With the [`unity` CLI](https://docs.unity.com/en-us/unity-cli) +installed, `unity test TestProject --mode EditMode -- -nographics` runs the same suite against +whichever editor matches `ProjectVersion.txt` (`--allow-install` fetches it if missing). ### 4 — WebGL builds (Unity Test Runner via `HostBuild`) — `TestProject/build-*.sh` diff --git a/Docs~/plans/agent-component.md b/Docs~/plans/agent-component.md index 9390753..246e066 100644 --- a/Docs~/plans/agent-component.md +++ b/Docs~/plans/agent-component.md @@ -34,12 +34,12 @@ The design has to serve four personas without forcing the harder ones onto the e | **Designer** (no C#) | Drag, drop, wire UnityEvents in the Inspector | Write any code, understand `Awaitable`, know what a transport is | | **Scripter** (game logic in C#) | Component reference → `agent.SendUserMessage(...)`; subscribe to typed events | Re-implement lifecycle, single-session policy, subscribe/unsubscribe boilerplate | | **Engineer** (full control) | Component as opt-in convenience; low-level `Conversation` still available | Be forced through the component for advanced setups (custom transports, programmatic config, runtime tool composition) | -| **AI assistant** (via [unity-mcp](https://github.com/Unity-Technologies/unity-mcp)) | Add the component, wire dependencies (AudioSource, colliders), configure providers, run a self-test — all without a human pointing and clicking | Reverse-engineer the component by trial-and-error; fight `[SerializeReference]` ergonomics through `SerializedObject`; parse free-form `Debug.Log` to know what happened | +| **AI assistant** (via the [`unity` CLI](https://docs.unity.com/en-us/unity-cli) or its `unity mcp` server) | Add the component, wire dependencies (AudioSource, colliders), configure providers, run a self-test — all without a human pointing and clicking | Reverse-engineer the component by trial-and-error; fight `[SerializeReference]` ergonomics through `SerializedObject`; parse free-form `Debug.Log` to know what happened | Two of these rows are load-bearing: - **Engineer:** the component is *additive over* `Conversation`, not a replacement. A user who needs full control still writes `Conversation.StartSessionAsync(options)` directly — the new component is one of several ways to use the SDK. -- **AI assistant:** for users working in Claude Code + unity-mcp this is arguably the *primary* onboarding path, not a fallback. "Add an ElevenLabs agent to my NPC" should scaffold + wire + self-test without the human ever opening the Inspector. See [AI-assisted authoring](#ai-assisted-authoring-via-unity-mcp). +- **AI assistant:** for users working in Claude Code + the `unity` CLI this is arguably the *primary* onboarding path, not a fallback. "Add an ElevenLabs agent to my NPC" should scaffold + wire + self-test without the human ever opening the Inspector. See [AI-assisted authoring](#ai-assisted-authoring-via-the-unity-cli). ## What the current TalkingBox tells us @@ -297,9 +297,9 @@ public async Awaitable StartSessionAsync(CancellationToken ct = default) --- -## AI-assisted authoring via unity-mcp +## AI-assisted authoring via the Unity CLI -[unity-mcp](https://github.com/Unity-Technologies/unity-mcp) gives an AI assistant the ability to add components, configure their fields, run editor scripts (`Unity_RunCommand`), generate assets (`Unity_AssetGeneration_GenerateAsset`), read the Console (`Unity_GetConsoleLogs`), and capture scene state (`Unity_SceneView_*`). A user in Claude Code can say *"add an ElevenLabs agent to my NPC character with push-to-talk"* and the assistant runs the scaffolding end-to-end. Designing the component with this workflow in mind costs little and makes the AI path durable instead of fragile. +The [`unity` CLI](https://docs.unity.com/en-us/unity-cli) drives a running Editor through the `com.unity.pipeline` package: an AI assistant can add components (`unity command add_component`), set serialized fields, run editor C# (`eval` / `eval_file`), read the Console (`console`), capture scene state (`capture_scene_view`), and run tests (`run_tests`). `unity mcp` serves the same catalog as MCP tools. A user in Claude Code can say *"add an ElevenLabs agent to my NPC character with push-to-talk"* and the assistant runs the scaffolding end-to-end. Designing the component with this workflow in mind costs little and makes the AI path durable instead of fragile. ### Concrete design consequences @@ -321,7 +321,7 @@ public sealed record AgentSetupOptions( bool EnforceSingleSession = true); ``` -`Configure` is the single entry point AI scripts call. It's add-or-find for the component, add-or-find for the `AudioSource`, set-not-replace for the override toggles. Re-running with the same input is a no-op. Re-running with changed input mutates exactly the deltas. Without this, every `Unity_RunCommand` script reinvents the same `GetComponent ?? AddComponent` dance and inevitably leaves orphan state on the second run. +`Configure` is the single entry point AI scripts call. It's add-or-find for the component, add-or-find for the `AudioSource`, set-not-replace for the override toggles. Re-running with the same input is a no-op. Re-running with changed input mutates exactly the deltas. Without this, every `unity command eval` snippet reinvents the same `GetComponent ?? AddComponent` dance and inevitably leaves orphan state on the second run. **2. Introspection that round-trips through chat.** A `string DescribeConfiguration()` method (and structured `AgentConfigSnapshot` record behind it) so the assistant can read the component back without parsing serialized YAML: @@ -351,7 +351,7 @@ The hash-not-value rule for the agent ID is deliberate — secrets shouldn't end [ElevenLabsAgent:{name}] tool '' invoked params= ``` -`Unity_GetConsoleLogs` returns these as grep targets. The convention already exists informally (`[ConvSmoke]`, `[TalkingBox:{name}]`); formalising it in the component is one line of code and a sentence in the docs. +`unity command console` returns these as grep targets. The convention already exists informally (`[ConvSmoke]`, `[TalkingBox:{name}]`); formalising it in the component is one line of code and a sentence in the docs. **4. Programmatic ergonomics for `[SerializeReference]`.** Polymorphic credential providers are great for human Inspector use but painful via `SerializedObject` — `ManagedReferenceUtility.SetManagedReference` plus type-string lookup is error-prone, and a misspelled type name silently produces a null field. Two mitigations: @@ -375,6 +375,8 @@ public sealed record HealthReport( Resolves the credential, opens a session, waits for `Connected`, sends a one-token contextual update, ends the session. Reports timings on success, the error on failure. The AI runs this after `Configure` and surfaces the result back to the human — *"agent configured; self-test passed in 312 ms"*. Same method is a `[ContextMenu("Run Self-Test")]` for humans. Same method is the basis for the CI smoke check that already lives in `Samples/ConversationSmokeTest/`. +**6. Register the setup surface as Pipeline commands.** An Editor-assembly wrapper tags `Configure`, `Describe` and `RunSelfTestAsync` with `[CliCommand]` (e.g. `elevenlabs_agent_configure`, `elevenlabs_agent_describe`, `elevenlabs_agent_self_test`), so the assistant calls them by name with a typed parameter schema instead of composing C# for `eval`. They then show up in `unity command` / `unity list` and as `unity mcp` tools, and CI can run them headlessly with `unity run --command `. The wrapper compiles only when `com.unity.pipeline` is present (asmdef `versionDefines`), so the package doesn't take a hard dependency on it. + ### Sample scenes become scaffold scripts Hand-built `.unity` files for samples are binary YAML — painful to diff, painful to regenerate, painful for the AI to modify. The same samples expressed as scaffold scripts: @@ -397,19 +399,19 @@ public static class QuickStartScaffold } ``` -…are trivial diffs (one C# file), trivially regenerable (re-run the menu item), AI-mutable via `Unity_RunCommand`, and self-document the recommended setup. Ship the scaffold scripts; let users (or the AI) generate the `.unity` files locally. The `.unity` files themselves stay gitignored under `Samples~/Generated/`. +…are trivial diffs (one C# file), trivially regenerable (re-run the menu item), AI-mutable via `unity command eval` / `menu`, and self-document the recommended setup. Ship the scaffold scripts; let users (or the AI) generate the `.unity` files locally. The `.unity` files themselves stay gitignored under `Samples~/Generated/`. -Asset generation for those samples — character sprites, sound effects, the talking-box's pulse audio — is `Unity_AssetGeneration_GenerateAsset` territory. Document one example end-to-end (*"have Claude generate a wizard sprite and wire it to the agent"*) rather than pretending the SDK ships pre-baked assets. +Asset generation for those samples — character sprites, sound effects, the talking-box's pulse audio — sits outside the Pipeline command set; the assistant generates them with an asset-generation tool (e.g. ElevenLabs sound effects) and brings them in with `unity command import_asset`. Document one example end-to-end (*"have Claude generate a wizard sprite and wire it to the agent"*) rather than pretending the SDK ships pre-baked assets. ### How the AI knows about the component -The setup helpers + introspection + self-test surface above are necessary but not sufficient — an AI assistant first has to *know* this SDK exists, what idioms it uses, and which APIs to call. That's what coding-agent skills are for, and the strategy is its own plan: [`unity-coding-agent-skills.md`](./unity-coding-agent-skills.md). The relevant takeaway here is that one of the planned skills — `elevenlabs:unity-agent-component` — is built *around* the `ElevenLabsAgentSetup.Configure` / `Describe` / `RunSelfTestAsync` API surface this section proposes. Without those primitives the skill body would devolve into "tell the user where to click in the Inspector"; with them, the skill emits ready-to-run `Unity_RunCommand` recipes. The component plan and the skills plan are co-evolving for this reason. +The setup helpers + introspection + self-test surface above are necessary but not sufficient — an AI assistant first has to *know* this SDK exists, what idioms it uses, and which APIs to call. That's what coding-agent skills are for, and the strategy is its own plan: [`unity-coding-agent-skills.md`](./unity-coding-agent-skills.md). The relevant takeaway here is that one of the planned skills — `elevenlabs:unity-agent-component` — is built *around* the `ElevenLabsAgentSetup.Configure` / `Describe` / `RunSelfTestAsync` API surface this section proposes. Without those primitives the skill body would devolve into "tell the user where to click in the Inspector"; with them, the skill emits ready-to-run `unity command` recipes. The component plan and the skills plan are co-evolving for this reason. ### The Editor wizard, reframed -The "Editor wizard / setup window" originally deferred in this plan reframes: **the AI assistant is the wizard** for users who work in Claude Code + unity-mcp. The docs guide them with *"tell Claude: 'set up a push-to-talk agent on the player character'"* instead of *"open the wizard, click Next, fill these fields."* +The "Editor wizard / setup window" originally deferred in this plan reframes: **the AI assistant is the wizard** for users who work in Claude Code + the `unity` CLI. The docs guide them with *"tell Claude: 'set up a push-to-talk agent on the player character'"* instead of *"open the wizard, click Next, fill these fields."* -A native Editor wizard still has a place for users who don't have unity-mcp configured — but it moves from "primary path" to "fallback for non-AI users." That softens the timing: ship the AI-friendly setup APIs in v0.2 alongside the component itself, defer the GUI wizard to v0.3 when there's real user data on which subset of fields actually trip people up. +A native Editor wizard still has a place for users who don't drive Unity from a coding agent — but it moves from "primary path" to "fallback for non-AI users." That softens the timing: ship the AI-friendly setup APIs in v0.2 alongside the component itself, defer the GUI wizard to v0.3 when there's real user data on which subset of fields actually trip people up. --- @@ -431,7 +433,7 @@ Each sample ships as a scaffold script per [Sample scenes become scaffold script ## What this plan deliberately defers - **Visual scripting integration** (Unity Visual Scripting / Bolt). UnityEvents already give designers a hook; visual-scripting nodes are polish for whichever version we hear consistent demand at. -- **Native Editor wizard / setup window.** A right-click `GameObject ▸ ElevenLabs ▸ Agent` menu item ships with the component. A full GUI wizard defers to v0.3 — the [AI-assisted authoring](#ai-assisted-authoring-via-unity-mcp) path covers the same need for unity-mcp users, and a GUI wizard for non-AI users is better designed after real feedback on which fields actually trip people up. +- **Native Editor wizard / setup window.** A right-click `GameObject ▸ ElevenLabs ▸ Agent` menu item ships with the component. A full GUI wizard defers to v0.3 — the [AI-assisted authoring](#ai-assisted-authoring-via-the-unity-cli) path covers the same need for coding-agent users, and a GUI wizard for non-AI users is better designed after real feedback on which fields actually trip people up. - **Persistent / cross-scene sessions.** `DontDestroyOnLoad` on an `ElevenLabsAgent` should Just Work, but stateful persistence (conversation token reuse, transcript replay across scenes) is a separate design. - **Component pooling.** 50 NPCs each with an `ElevenLabsAgent` ≠ 50 concurrent sessions — the single-session policy handles that — but "one shared session that swaps between NPCs as the player walks past" is a separate pattern. - **ScriptableObject preset templates.** Useful for "all our merchant agents share these overrides"; ship later if users report duplication pain. diff --git a/Docs~/plans/unity-coding-agent-skills.md b/Docs~/plans/unity-coding-agent-skills.md index cb1c187..9fd4f75 100644 --- a/Docs~/plans/unity-coding-agent-skills.md +++ b/Docs~/plans/unity-coding-agent-skills.md @@ -29,7 +29,7 @@ Five skills cover the surface a Unity developer reaches for, grouped by when the | Slug | Trigger phrasing | Body covers | |---|---|---| | `elevenlabs:unity-quickstart` | "Add ElevenLabs to my Unity project", "use ElevenLabs in Unity", "set up the ElevenLabs Unity SDK" | UPM install (Git URL or OpenUPM); `ELEVENLABS_API_KEY` for backend token-signing; minimal `Conversation.StartSessionAsync` example; pointer to either the component skill (drag-and-drop) or direct API (custom integrations) | -| `elevenlabs:unity-agent-component` | "Drag-and-drop an ElevenLabs agent on a GameObject", "add an ElevenLabs agent component", "make my NPC talk with ElevenLabs" | Full `ElevenLabsAgent` component story; `ElevenLabsAgentSetup.Configure` + `AgentSetupOptions`; credential provider picking; `AudioSource` wiring; `RunSelfTestAsync` after scaffold. Heavy use of `Unity_RunCommand` to perform the setup | +| `elevenlabs:unity-agent-component` | "Drag-and-drop an ElevenLabs agent on a GameObject", "add an ElevenLabs agent component", "make my NPC talk with ElevenLabs" | Full `ElevenLabsAgent` component story; `ElevenLabsAgentSetup.Configure` + `AgentSetupOptions`; credential provider picking; `AudioSource` wiring; `RunSelfTestAsync` after scaffold. Performs the setup through `unity command` against the live Editor | | `elevenlabs:unity-client-tools` | "Register a client tool in Unity", "let the ElevenLabs agent call my C# code", "make the agent control my scene" | Typed `RegisterTool` patterns (sync + async overloads); designer-tier `SimpleToolBinding` list on the component; `ClientToolException` error pattern; the reply-vs-fire-and-forget tradeoff | ### Ship with v0.3+ (when there's content to migrate or when audio routing widens) @@ -60,7 +60,7 @@ Matches `elevenlabs:sdk-migration`'s shape: ```yaml --- name: elevenlabs:unity-agent-component -description: Drag-and-drop an ElevenLabs conversational agent onto a Unity GameObject. Use when adding voice AI to a Unity scene, configuring credential providers (public agent / signed URL / WebRTC token), wiring AudioSource for native playback, registering client tools, or scaffolding the setup via unity-mcp. +description: Drag-and-drop an ElevenLabs conversational agent onto a Unity GameObject. Use when adding voice AI to a Unity scene, configuring credential providers (public agent / signed URL / WebRTC token), wiring AudioSource for native playback, registering client tools, or scaffolding the setup via the unity CLI. license: MIT compatibility: Requires Unity 6 LTS or later, the elevenlabs-unity package (>=0.2.0), and either a manually-configured agent id or a backend that returns signed URLs / conversation tokens. metadata: {"openclaw": {"requires": {"env": []}}} @@ -78,7 +78,7 @@ Three deliberate choices to call out: The packages-repo skill body follows: short intro → numbered migration order → per-API section with Before/After code blocks. Translating that to the Unity skill set: - **Quickstart skill:** install → credential setup → minimal example → pointer to the next skill ("for drag-and-drop, see `elevenlabs:unity-agent-component`"). -- **Component skill:** when to use the component vs raw `Conversation` → `ElevenLabsAgentSetup.Configure` recipe with `Unity_RunCommand` block → credential provider picking → `AudioSource` wiring → `RunSelfTestAsync` to verify. References point to `agent-component.md` for the design rationale. +- **Component skill:** when to use the component vs raw `Conversation` → `ElevenLabsAgentSetup.Configure` recipe as a `unity command` call → credential provider picking → `AudioSource` wiring → `RunSelfTestAsync` to verify. References point to `agent-component.md` for the design rationale. - **Client-tools skill:** typed vs designer-tier registration → sync/async overloads → error handling → reply contracts → reference to the upstream agent-side tool definition docs. - **Migration skill:** one section per version pair (v0.2→v0.3, v0.3→v0.4, …) with Before/After code blocks. The migration-order numbering matches the JS precedent exactly. - **Audio-routing skill:** decision tree (WebGL? want spatial audio? want mixer routing? → default-mode vs Unity-routed-mode) → opt-in code → mode-specific gotchas. @@ -95,27 +95,23 @@ Each skill's `references/` is for content that would bloat the main body but the These are the patterns that distinguish Unity-skill content from the generic `elevenlabs/skills` body shape: -**Explicit unity-mcp tool callouts.** The generic skills don't know `Unity_RunCommand` exists. The Unity skills lean in: - -> Use `Unity_RunCommand` to add and configure the component in one call: -> ```csharp -> internal class CommandScript : IRunCommand -> { -> public void Execute(ExecutionResult result) -> { -> var go = GameObject.Find("NPC"); -> var agent = ElevenLabsAgentSetup.Configure(go, new AgentSetupOptions( -> Credentials: CredentialKind.PublicAgent, -> AgentId: "agent_xxx", -> AddAudioSource: true)); -> result.RegisterObjectCreation(agent); -> result.Log("Configured {0}", agent); -> } -> } +**Explicit Unity CLI callouts.** The generic skills don't know a live Editor can be driven from the terminal. The Unity skills lean in on the [`unity` CLI](https://docs.unity.com/en-us/unity-cli) (or the same commands as MCP tools via `unity mcp`): + +> Add and configure the component in one call, using the Pipeline command the SDK registers (see [agent-component.md → design consequence 6](./agent-component.md#concrete-design-consequences)): +> ```bash +> unity command elevenlabs_agent_configure --target NPC --credentials PublicAgent \ +> --agent_id agent_xxx --add_audio_source true +> ``` +> or, without the registered command, through `eval`: +> ```bash +> unity command eval 'var agent = ElevenLabs.Agents.ElevenLabsAgentSetup.Configure( +> UnityEngine.GameObject.Find("NPC"), +> new AgentSetupOptions(Credentials: CredentialKind.PublicAgent, AgentId: "agent_xxx", AddAudioSource: true)); +> return agent.DescribeConfiguration();' > ``` -> Verify with `Unity_GetConsoleLogs` for the `[ElevenLabsAgent:NPC] configured` line. +> Verify with `unity command console --level log` for the `[ElevenLabsAgent:NPC] configured` line. -The skill can degrade gracefully when unity-mcp isn't available — same recipe, but the AI walks the user through the Inspector clicks instead. +The skill needs the project to have `com.unity.pipeline` (`unity pipeline install`). It can degrade gracefully when the CLI or package isn't available — same recipe, but the AI walks the user through the Inspector clicks instead. **C# idioms over JS/Python.** `Awaitable` not `Promise`, `MonoBehaviour` lifecycle not React hooks, `[SerializeReference]` not class-component-with-state. The skill's example code is C# only. @@ -152,9 +148,9 @@ Open question: does that hybrid model (source-of-truth here, distribution there) `elevenlabs/skills` ships an `evals/` directory at the repo root (trigger evals + functional evals). Mirroring that here means each skill carries: - **Trigger evals** — prompts that should fire the skill ("how do I add an ElevenLabs agent to Unity") vs prompts that should not ("how do I add an animation to a Unity GameObject"). Confirms the `description` field is well-tuned. -- **Functional evals** — given the skill is loaded, does the AI produce working scaffolding code? For the component skill: does the produced `Unity_RunCommand` script actually compile and configure the component correctly when executed? This is the same shape as the existing `IntegrationTests~/` Playwright smokes, but for skill output. +- **Functional evals** — given the skill is loaded, does the AI produce working scaffolding code? For the component skill: does the produced `unity command` recipe actually compile and configure the component correctly when executed? This is the same shape as the existing `IntegrationTests~/` Playwright smokes, but for skill output. -Eval runner: tentatively reuse the `pnpm --dir IntegrationTests~/` infrastructure (Vitest already there; Playwright already there for running browser-side checks if a skill produces a WebGL flow). The Unity-side functional eval needs `Unity_RunCommand` to actually execute the produced script against `TestProject/`. That's a meaningful new test surface — probably its own follow-up plan once the first skill ships. +Eval runner: tentatively reuse the `pnpm --dir IntegrationTests~/` infrastructure (Vitest already there; Playwright already there for running browser-side checks if a skill produces a WebGL flow). The Unity-side functional eval can execute the produced recipe headlessly against `TestProject/` with `unity run TestProject --command ` (or `eval_file` on a resident batch Editor), so no GUI Editor is needed. It's still a meaningful new test surface — probably its own follow-up plan once the first skill ships. For v0.2, ship skills without evals and add the eval suite incrementally; for v1.0, every shipped skill has both trigger and functional evals before promotion.