Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentations/dynamic-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This document defines how Unity Editor code exposes Custom Project Tools through

- Custom Project Tools are declared to in-editor ACP sessions only when **Project Settings > DotCraft > Agent** is `DotCraft`.
- `Custom ACP` agents connected through the in-editor chat do not receive these tools, even if they support ACP extension methods.
- Enabled Custom Project Tools are also available through the standalone MCP/CLI with `--backend gateway`. Attach exposes only C# execution and does not discover or dispatch custom project tools.
- Enabled Custom Project Tools are also available through the standalone MCP/CLI with `--backend gateway`. Attach exposes C# execution and recompilation and does not discover or dispatch custom project tools.
- Tools are discovered from loaded Editor assemblies that reference `DotCraft.Editor`.
- A tool method must be `static`, non-generic, and must not use `ref` or `out` parameters.
- Newly discovered custom tools default to disabled. Users enable them in **Project Settings > DotCraft > Unity Tools > Custom Project Tools**.
Expand Down
6 changes: 3 additions & 3 deletions Plugins/dotcraft-unity/skills/dotcraft-unity/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This skill governs Unity tool calls, especially `unity_execute_csharp`. It does

Prefer the available dotcraft-unity MCP tools. When MCP is not configured or not exposed in the current session, use `dotcraft-unity.exe` from PATH. Read `references/cli.md` for installation, project selection, command syntax, and result handling. CLI needs no MCP configuration. `--backend auto` selects Gateway when discovery or manifest state exists and Attach otherwise; explicit `gateway` and `attach` are available. Gateway requires the UPM integration; Attach requires a Windows x64 Mono Editor and can execute C# without UPM.

Choose the transport before issuing the call. If a dispatched MCP or CLI call fails, times out, or disconnects, do not switch transports and replay it automatically: Unity may already have executed it. Gateway exposes its enabled tool registry. Attach exposes only `unity_execute_csharp`, not custom project tools. `status` and tool listing are read-only and never attach; `--pid` selects an Editor when needed.
Choose the transport before issuing the call. If a dispatched MCP or CLI call fails, times out, or disconnects, do not switch transports and replay it automatically: Unity may already have executed it. Gateway exposes its enabled tool registry. Attach exposes `unity_execute_csharp` and `unity_recompile`, not custom project tools. `status` and tool listing are read-only and never attach; `--pid` selects an Editor when needed.

## Inline code or a saved script

Expand All @@ -39,7 +39,7 @@ Write project-specific scripts to `.craft/scripts/`. Before writing one, check w

Load only the reference needed for the current task:

- **Editor window screenshots**: Read `references/gameview-screenshot.md` when the user asks to capture, compare, inspect, or save what a Unity Editor window looks like — GameView, SceneView, or the whole Editor.
- **Editor window screenshots**: Read `references/gameview-screenshot.md` when the user asks to capture, compare, inspect, or save what a Unity Editor window looks like — GameView, SceneView, or the whole Editor — or to render a specific camera viewpoint.
- **Console reading**: Read `references/console-reading.md` when the user asks to inspect, summarize, search, or diagnose Unity Console logs.
- **API helpers**: Read `references/api.md` when a `unity_execute_csharp` snippet needs loaded-type lookup or third-party component reflection.
- **Snippet failure modes**: Read `references/snippet-craft.md` when a snippet times out, returns a wall of stack traces, fails to compile, or when the effect of a call is not visible in the same call.
Expand All @@ -51,7 +51,7 @@ Keep Unity in the background: prefer read-only inspection first, then make the s

## Waiting for Editor readiness

For script compilation and Domain Reload, choose the backend-specific workflow in `references/compilation-and-reload.md`: Gateway durable operations or Attach native lifecycle recovery. Gateway uses its external PowerShell waiter; Attach verifies a fresh bridge handshake through the selected backend. Then issue a fresh bounded read-only call. Do not infer readiness from window focus, progress bars, Editor log silence, or a fixed delay.
For script compilation and Domain Reload, choose the backend-specific workflow in `references/compilation-and-reload.md`: Gateway durable operations or Attach `unity_recompile` (CLI `recompile`). Gateway uses its external PowerShell waiter; Attach's recompile waits for its own compilation and the bridge recovery after it. Then issue a fresh bounded read-only call. Do not infer readiness from window focus, progress bars, Editor log silence, a fixed delay, or `EditorApplication.isCompiling` read right after a request: it is still false before the requested compilation starts. Any wait loop stops on a field the tool or script reports as terminal, never on a guessed state name, file name or time of day.

For asset import or asynchronous Shader compilation that does not reload managed assemblies, use bounded external readiness probes. Require `EditorApplication.isUpdating` and `ShaderUtil.anythingCompiling` to be false in two consecutive successful probes before reporting readiness.

Expand Down
29 changes: 20 additions & 9 deletions Plugins/dotcraft-unity/skills/dotcraft-unity/references/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ that same Windows x64 executable for external compilation. Gateway also exposes
project tools.

Attach executes C# without installing the UPM package or enabling its Gateway. Pass `--pid` when
multiple Editors match the project. Attach exposes only `unity_execute_csharp`; use Gateway for
custom project tools, in-editor chat, and UPM durable-operation helpers. Calls do not install
packages or download another executable.
multiple Editors match the project. Attach exposes `unity_execute_csharp` and `unity_recompile`;
use Gateway for custom project tools, in-editor chat, and UPM durable-operation helpers. Calls do
not install packages or download another executable.

## Project and readiness

Expand All @@ -46,8 +46,11 @@ dotcraft-unity exec --code 'return Application.unityVersion;' --project-root $pr
Pass the intended project explicitly when the agent is working outside that Unity project. Otherwise, CLI commands walk upward from the current directory to the nearest directory containing both `Assets` and `ProjectSettings`. MCP always requires `--project-root`.

`status` and `tools list` / `tools describe` never inject. Gateway status validates discovery and
checks TCP reachability with a two-second limit. Attach status lists matching project/PID candidates.
Neither establishes Editor readiness. An `exec` version probe performs a real call and can attach
checks TCP reachability with a two-second limit. Attach status lists matching project/PID candidates
with `editorState`, the record each bridge last published: connection, play mode, compiling and
reloading flags, and the latest compilation's sequence and outcome. It is absent until the Editor
has been attached, and a `connection` other than `Ready` means it stopped at the last Domain
Reload. Neither status establishes Editor readiness. An `exec` version probe performs a real call and can attach
when Attach is selected. Do not print discovery files: they contain private authentication tokens.

## Execute C#
Expand Down Expand Up @@ -104,7 +107,15 @@ can report `UnityExecutionLost` or `UnityOutcomeUnknown` when a dispatched execu
be recovered. Never automatically replay a dispatched request.

For compilation or Domain Reload, follow [compilation-and-reload.md](compilation-and-reload.md).
Gateway uses the UPM durable operation script and external waiter. Attach uses native lifecycle
recovery to establish a fresh bridge; its old execution is still lost and must not be replayed.
After recovery, make a new read-only probe and inspect the Console. A connection alone does not
prove that compilation succeeded.
Gateway uses the UPM durable operation script and external waiter. Attach uses `recompile`:

```powershell
dotcraft-unity recompile --backend attach --pid 12345 --project-root $projectRoot --json
```

It sends one request, then waits up to `--wait-ms` (default 60000, at most 120000) for that
compilation and the bridge recovery after its Domain Reload. Success reports whether a reload
happened. `CompilationFailed` carries the compiler errors in `result.errors`.
`RecompileStillRunning` means the wait ended first: do not run it again. Follow
[compilation-and-reload.md](compilation-and-reload.md) to wait for it. After success, make a new
read-only probe and inspect the Console.
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,28 @@ The old execution/context is invalid and may report lost or unknown. Recovery re
connection, not the execution or its result. If recovery is unavailable or no fresh handshake
arrives, report that failure instead of repeatedly injecting or resending the mutation.

Request compilation or reload only once, after authorized source changes:

```csharp
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
return "compilation requested";
```

For reload without compilation, use `UnityEditor.EditorUtility.RequestScriptReload()` instead.
Afterward, issue a new bounded read-only probe through the same backend and selected project/PID.
The host can reconnect to the recovered bridge before this independent call. Verify that the
Editor is not compiling or importing, then inspect the Console. Compilation can fail without a
Domain Reload, so a connection or unchanged generation alone does not establish success.
After authorized source changes, call `unity_recompile` once, or `dotcraft-unity recompile` from
the CLI (see [cli.md](cli.md)). It imports changed assets, requests one script compilation and
waits for that compilation, identified by its sequence number, rather than one that was already
running:

- Success with `domainReloaded: true` means the new scripts are loaded and the bridge recovered;
`domainReloaded: false` means the Editor settled without a reload.
- `CompilationFailed` returns the compiler errors. Fix them and recompile again.
- `RecompileStillRunning` means the wait ended first. Do not request it again. A compilation
publishes its new sequence when it starts, so a newer sequence alone does not mean it finished.
Read Attach `status` until `compile` has a sequence newer than the reported baseline with
`finished: true`, then check `succeeded`.
- `UnityOutcomeUnknown` means the request may or may not have reached Unity. Read `status` instead
of requesting it again.

Do not request compilation from a snippet and then poll `EditorApplication.isCompiling`: it is
still false before the requested compilation starts, so the first probe can pass before anything
compiled. If the installed CLI has no `recompile` command, update it first.

For reload without compilation, call `UnityEditor.EditorUtility.RequestScriptReload()` once from a
snippet. Afterward, issue a new bounded read-only probe through the same backend and selected
project/PID. Verify that the Editor is not compiling or importing, then inspect the Console.

`DcuLongRunningOperation`, `unity-operation.cs`, and `Wait-DotCraftUnityOperation.ps1` belong to
UPM/Gateway. Do not call them on Attach-only Editors. Work that must resume after reload needs a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,87 +6,46 @@ window looks like — GameView, SceneView, or the Editor as a whole.
## Capture From Outside The Process

Capture the Editor window at the OS level, not through Unity. Nothing is focused, shown or
re-laid-out, and no extra render is forced — which matters when the screenshot documents a memory
or GPU-memory measurement it would otherwise perturb.
re-laid-out, and no extra render is forced, which matters when the screenshot documents a memory
or GPU measurement it would otherwise perturb.

This captures the whole Editor window, not a single pane. See
[Cropping to one pane](#cropping-to-one-pane) if a single pane is required.

## Windows

`PrintWindow` with `PW_RENDERFULLCONTENT` (flag `2`) — the flag is required for
hardware-accelerated windows, which the Editor is.
On Windows, run the bundled `scripts/Capture-EditorWindow.ps1` from the agent process:

```powershell
param([int]$TargetPid, [string]$Out)
Add-Type -AssemblyName System.Drawing
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class WinCap {
[DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr dc, uint flags);
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
[DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; }
}
"@
$h = (Get-Process -Id $TargetPid).MainWindowHandle
if ($h -eq [IntPtr]::Zero) { throw "no main window" }
if ([WinCap]::IsIconic($h)) { throw "window is minimised - capture would be blank" }

$r = New-Object WinCap+RECT
[void][WinCap]::GetWindowRect($h, [ref]$r)
$w = $r.R - $r.L; $ht = $r.B - $r.T
$bmp = New-Object System.Drawing.Bitmap $w, $ht
$gfx = [System.Drawing.Graphics]::FromImage($bmp)
$hdc = $gfx.GetHdc()
try { $ok = [WinCap]::PrintWindow($h, $hdc, 2) } finally { $gfx.ReleaseHdc($hdc) }
if (-not $ok) { $gfx.CopyFromScreen($r.L, $r.T, 0, 0, (New-Object System.Drawing.Size $w, $ht)) }
$bmp.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)
$gfx.Dispose(); $bmp.Dispose()
"saved $Out (${w}x${ht}, PrintWindow=$ok)"
```

Get the process id from the Editor itself so the right instance is captured when several are
running:

```csharp
return System.Diagnostics.Process.GetCurrentProcess().Id;
pwsh -File <skill>/scripts/Capture-EditorWindow.ps1 -TargetPid <editor pid> -Out <absolute .png>
```

The `CopyFromScreen` fallback only produces a correct image if the window is unoccluded, so treat
a `PrintWindow=False` result as lower-confidence evidence.
Take the PID from the selected Editor (`status --pid`, or `System.Diagnostics.Process.GetCurrentProcess().Id`
in a snippet) so the right instance is captured when several are running. The result reports
`PrintWindow=False` when it fell back to copying the screen, which is only correct for an
unoccluded window; treat that image as lower-confidence evidence.

## macOS

`screencapture -x -l <windowID> out.png` captures a single window without the shutter sound.
Resolve the window id via `CGWindowListCopyWindowInfo` (for example through a small Swift or
Python helper) filtered by the Unity process id.
On macOS, `screencapture -x -l <windowID> out.png` captures one window; resolve the window ID with
`CGWindowListCopyWindowInfo` filtered by the Unity process ID.

## Cropping To One Pane

When a single pane is genuinely needed, read its rectangle from the Editor and crop the full-window
capture to it. `EditorWindow.position` is public API:
When a single pane is genuinely needed, run `scripts/editor-window-rect.cs` (`windowType`,
default `UnityEditor.GameView`) to read the pane's rectangle, then pass
`-Crop "x,y,width,height"` to the capture script. The rectangle is in the Editor's coordinate
space; window decoration, docking and display scaling offset it on the bitmap. Calibrate the
offset once by capturing, cropping and checking the result; it then stays stable for that layout.

```csharp
using System.Linq;
var t = Dcu.Type("UnityEditor.SceneView", throwIfMissing:false); // or the window type you want
if (t == null) return "window type not present";
var win = Resources.FindObjectsOfTypeAll(t).OfType<EditorWindow>().FirstOrDefault();
return win == null ? "not open" : win.position.ToString();
```
## Rendering A Camera Viewpoint

Verify the coordinate mapping once on the target setup before relying on it: the rect is reported
in the Editor's own coordinate space, and how that maps onto the captured bitmap depends on window
decoration, docking and display scaling. Capture once, crop, and eyeball the result — after that
the offset is stable for that layout.
When the user needs a specific viewpoint rather than what the window shows, run
`scripts/camera-render.cs` with `out` (absolute `.png` path) and optionally `camera`, `width`,
`height` and `frames`. It renders at the Game view size, waits out temporal history, disables
asynchronous shader compilation while rendering, and restores the camera afterwards. Position the
camera in a separate call first if needed. It forces extra renders, so do not use it as evidence
for a performance or memory measurement.

## Reading The Result

Check the image rather than assuming success. A blank or black capture is a signal worth acting
on, not something to retry: a minimised or long-backgrounded Editor may not be redrawing at all,
which also invalidates any measurement taken at that moment.

The output should show the Editor window as it appears on screen. If the user asked specifically
about GameView content and no camera is rendering, the capture will faithfully show that — check
`Camera.allCamerasCount` before concluding the capture failed.
A uniform or unexpected image can be the real view: compare it with a window capture before
concluding that the render failed. If no camera is rendering, the capture faithfully shows that;
check `Camera.allCamerasCount`.
Loading
Loading